Compare commits
260 commits
fix/ci-bui
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a707e6fd05 | ||
|
|
d55ed5221c | ||
|
|
a5af0bddb8 | ||
|
|
9611038048 | ||
|
|
06dde39cfd | ||
|
|
69496c1f3c | ||
|
|
ce6f201fcd | ||
|
|
73a93a29bb | ||
|
|
ee7e30e67c | ||
|
|
57ebd3daed | ||
|
|
82214b6ed0 | ||
|
|
2b9956d97b | ||
|
|
b2c7e23a57 | ||
|
|
468311bbba | ||
|
|
7f90a61ca5 | ||
|
|
17e1b9220f | ||
|
|
34b4a9f30c | ||
|
|
abb1c6ddd8 | ||
|
|
32421df002 | ||
|
|
f16af054be | ||
|
|
3a6abc9a79 | ||
|
|
a836fd1e73 | ||
|
|
0190e2d468 | ||
|
|
ea21c20d18 | ||
|
|
ad10c8a2a1 | ||
|
|
65d6ae239a | ||
|
|
415f0066d7 | ||
|
|
4f3c599c3b | ||
|
|
5e8e4538f8 | ||
|
|
64f83dcd5c | ||
|
|
cd418c2d79 | ||
|
|
0267d095e2 | ||
|
|
2d62630e30 | ||
|
|
750a4cd5b0 | ||
|
|
baacefef54 | ||
|
|
f96d53db9a | ||
|
|
720e84487e | ||
|
|
8616f04995 | ||
|
|
7152b06265 | ||
|
|
2b3510f55d | ||
|
|
d1334d0ec0 | ||
|
|
853564ebe9 | ||
|
|
3bf2f76c56 | ||
|
|
6f707af677 | ||
|
|
1abaaff7d1 | ||
|
|
3aa888bc26 | ||
|
|
d57081364b | ||
|
|
4599c2d96f | ||
|
|
14a8067aea | ||
|
|
d719e15cf8 | ||
|
|
7814cd0e82 | ||
|
|
469cdfb95e | ||
|
|
e90c20bfc5 | ||
|
|
fc9dd1ee9b | ||
|
|
c85761f92e | ||
|
|
c342b478c1 | ||
|
|
fc6772a124 | ||
|
|
743b6283c0 | ||
|
|
3562b0e730 | ||
|
|
26a356a158 | ||
|
|
a3ebf0259e | ||
|
|
df097bfc81 | ||
|
|
765c546c20 | ||
|
|
13bdcdad03 | ||
|
|
b083a66afd | ||
|
|
4115c3d4df | ||
|
|
c63eaa5a12 | ||
|
|
531cb9281c | ||
|
|
bff796d574 | ||
|
|
f7d1b2ea1d | ||
|
|
9a620545b4 | ||
|
|
6e7f5302e3 | ||
|
|
ec787e7cf5 | ||
|
|
8ef48f3608 | ||
|
|
2bddedb96c | ||
|
|
5d39748a28 | ||
|
|
1a9fd61ec3 | ||
|
|
a9a1dc4ad3 | ||
|
|
aab9fdc101 | ||
|
|
21029f5f72 | ||
|
|
64ffdf4f32 | ||
|
|
68c85dd812 | ||
|
|
fd84dd0320 | ||
|
|
7df631b799 | ||
|
|
3d45a8d70f | ||
|
|
2978e3b8d1 | ||
|
|
ffcc29796c | ||
|
|
79298ddf6c | ||
|
|
e0917110e1 | ||
|
|
1de8af79f0 | ||
|
|
5fe307a852 | ||
|
|
08bfa8199d | ||
|
|
2c94757947 | ||
|
|
85837531de | ||
|
|
3360ee33f1 | ||
|
|
d882007611 | ||
|
|
bd9116e1cf | ||
|
|
5e54c7bf61 | ||
|
|
bf6f3c0691 | ||
|
|
7eb893bcc9 | ||
|
|
52f05764f4 | ||
|
|
cdbbb3a957 | ||
|
|
457af73f88 | ||
|
|
6be3d89176 | ||
|
|
9267cb3918 | ||
|
|
f55d1a4a62 | ||
|
|
cc0ffe6fb2 | ||
|
|
acca6bfe47 | ||
|
|
8148f860e5 | ||
|
|
f0e9503354 | ||
|
|
4f779c9af0 | ||
|
|
202152bbf0 | ||
|
|
d55b9dcda0 | ||
|
|
6f6f2e4abc | ||
|
|
c3c21a26e0 | ||
|
|
5e2fd369fe | ||
|
|
7635ac1d90 | ||
|
|
5769fe2211 | ||
|
|
04a799e7f6 | ||
|
|
ed8d5fb7a8 | ||
|
|
8038be3023 | ||
|
|
511fd1df4e | ||
|
|
0b63dbbc9a | ||
|
|
85290a619e | ||
|
|
d1796e4c68 | ||
|
|
f92e36e7d4 | ||
|
|
cbf96e8b45 | ||
|
|
4537e7d56c | ||
|
|
782c40b5d3 | ||
|
|
21effc7f21 | ||
|
|
cc1f7fe682 | ||
|
|
f619b8702c | ||
|
|
b4c17583b6 | ||
|
|
ff308a190c | ||
|
|
c5f3b83cdf | ||
|
|
03c81d8cc9 | ||
|
|
3c4c9ca555 | ||
|
|
19bade53a0 | ||
|
|
351086110c | ||
|
|
5bc83206b2 | ||
|
|
8aa226d6ca | ||
|
|
02310df32f | ||
|
|
ffdd65f74d | ||
|
|
90b346b75a | ||
|
|
3619dafd47 | ||
|
|
12491b2235 | ||
|
|
2dd8151138 | ||
|
|
8c033fe878 | ||
|
|
f6d2384fa5 | ||
|
|
2621359a61 | ||
|
|
9f8c521659 | ||
|
|
2ffe2f510d | ||
|
|
f4d48fdf90 | ||
|
|
66bb7fce6a | ||
|
|
42636b29e8 | ||
|
|
e0cb50aaea | ||
|
|
f2371d1c7e | ||
|
|
01f52c1e2f | ||
|
|
8d1d6303d3 | ||
|
|
bf05c67966 | ||
|
|
4102b2298d | ||
|
|
2171fc59eb | ||
|
|
8389d19545 | ||
|
|
5fe672884e | ||
|
|
9481d80e5f | ||
|
|
3f309f58bb | ||
|
|
004bba80ae | ||
|
|
0fc969cf00 | ||
|
|
691db71d12 | ||
|
|
07265446cf | ||
|
|
86c9658318 | ||
|
|
8c584d7cfc | ||
|
|
f42f53b375 | ||
|
|
8c6c43429d | ||
|
|
a4c6e4871e | ||
|
|
f17cdc8fd0 | ||
|
|
3506cfc97d | ||
|
|
7d8a71eaf6 | ||
|
|
eeac21e2aa | ||
|
|
eb58fdfa00 | ||
|
|
44288a202d | ||
|
|
3196f8a0a4 | ||
|
|
63c12c7984 | ||
|
|
9a716efd37 | ||
|
|
136b6f7c46 | ||
|
|
63d8e86725 | ||
|
|
6cfb92faf1 | ||
|
|
f47d6d1d93 | ||
|
|
2eb7cbf84e | ||
|
|
3d6f61bb56 | ||
|
|
01fdbe19d7 | ||
|
|
4ecfdfce27 | ||
|
|
e198c43ce2 | ||
|
|
ee7c27d280 | ||
|
|
9a6886ad71 | ||
|
|
019075d927 | ||
|
|
6131c74016 | ||
|
|
52e10aca4c | ||
|
|
4b7192edfa | ||
|
|
ac5d0e551b | ||
|
|
d04d1d608d | ||
|
|
46e37bfe7e | ||
|
|
e9a2a8c51e | ||
|
|
e453be092b | ||
|
|
ced4d26d20 | ||
|
|
683137451b | ||
|
|
61af37117d | ||
|
|
fb8a6119f9 | ||
|
|
62513e7a04 | ||
|
|
007a6f8a59 | ||
|
|
aa0970913c | ||
|
|
4ae471a7b9 | ||
|
|
f19d654e87 | ||
|
|
7348da9aff | ||
|
|
9a6d328b47 | ||
|
|
afdda092bc | ||
|
|
6858113d54 | ||
|
|
adeba43dca | ||
|
|
660ee94361 | ||
|
|
ae7ab4fd21 | ||
|
|
cf531f247a | ||
|
|
c1e73bc602 | ||
|
|
ccd0a1f066 | ||
|
|
f210de66f2 | ||
|
|
1d0c42680f | ||
|
|
6e58a231e0 | ||
|
|
4d94dccdfc | ||
|
|
4ab61ffcc5 | ||
|
|
5eede3f588 | ||
|
|
28d9fbabc8 | ||
|
|
651e647f29 | ||
|
|
0a712869d5 | ||
|
|
3d4e9d052b | ||
|
|
a05d9a9b38 | ||
|
|
d0160e829f | ||
|
|
fb8756b371 | ||
|
|
2ddfdd3bfa | ||
|
|
078604ef25 | ||
|
|
0bacc54ac4 | ||
|
|
af9bd39358 | ||
|
|
67317dde13 | ||
|
|
eccadcdeff | ||
|
|
a854f81dda | ||
|
|
eeec556c9f | ||
|
|
48d1cd7807 | ||
|
|
be25ad7213 | ||
|
|
7a90e92892 | ||
|
|
c06ab2754d | ||
|
|
02d6302a8c | ||
|
|
76f8a3cd01 | ||
|
|
3905a1327d | ||
|
|
63892f2a8f | ||
|
|
62336a53a9 | ||
|
|
052edd13fa | ||
|
|
f9e29656fa | ||
|
|
f035c217e6 | ||
|
|
c4734b8b90 | ||
|
|
50994e9278 | ||
|
|
ed704eb7f8 | ||
|
|
f195d82414 |
39 changed files with 1611 additions and 663 deletions
2
.idea/.name
generated
2
.idea/.name
generated
|
|
@ -1 +1 @@
|
||||||
ShiggyXposed
|
GoonXposed
|
||||||
|
|
|
||||||
205
README.md
205
README.md
|
|
@ -1,191 +1,40 @@
|
||||||
# ShiggyXposed
|
# GoonXposed
|
||||||
|
|
||||||
ShiggyXposed is an Xposed module (Kotlin) that injects the ShiggyCord 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).
|
## Info
|
||||||
|
* Injects GoonCord client into the Discord mobile app.
|
||||||
---
|
* Built in Kotlin.
|
||||||
|
* Material 3 like Shake menu with some discord popup accents
|
||||||
## 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
|
|
||||||
|
|
||||||
ShiggyXposed injects a patched ShiggyCord 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 ShiggyCord 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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
To use this module, your device must meet the following requirements:
|
||||||
Before installing and using ShiggyXposed:
|
* **Root Access**: Provided by Magisk, KernelSU, KernelSUNext, or Sukisu Ultra.
|
||||||
|
* **Xposed Framework**: Use JingMatrix Fork of [LSPosed](https://github.com/JingMatrix/LSPosed).
|
||||||
- A rooted Android device. Supported root solutions you might use:
|
* **Target Application**: An official Discord application (stable, beta, or alpha version).
|
||||||
- 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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation
|
## 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:
|
The project is built with Kotlin and inherits its foundational structure from KettuXposed.
|
||||||
- https://github.com/kmmiio99o/ShiggyXposed/releases
|
|
||||||
2. Install the APK on your device (standard package install).
|
|
||||||
3. Open your LSPosed manager (or chosen Xposed manager).
|
|
||||||
4. Enable the ShiggyXposed 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.
|
|
||||||
|
|
||||||
---
|
# 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:
|
This project was made possible thanks to the work of:
|
||||||
- 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)
|
|
||||||
|
|
||||||
Themes:
|
· The LSPosed Team for the framework.
|
||||||
- Pick appearance: System / Light / Dark
|
· JingMatrix for their LSPosed fork.
|
||||||
- Choose Menu Color Flavor: blue, green, mocha, vanilla, purple, amber, teal
|
· [cocobo1](https://codeberg.org/cocobo1) for adjusting the Xposed module for Kettu client mod.
|
||||||
- Theme & flavor settings persist under the app's files directory (`files/logbox/LOGBOX_SETTINGS` and `files/logbox/LOGBOX_THEMES`)
|
· [Revenge Team](https://github.com/revenge-mod) for making a really good Xposed Module to base of
|
||||||
|
|
||||||
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 ShiggyXposed 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/kmmiio99o/ShiggyXposed/releases
|
|
||||||
- Repository: https://github.com/kmmiio99o/ShiggyXposed
|
|
||||||
- Support server: https://discord.gg/nQykFF9Ud6
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Thank you for using ShiggyXposed.
|
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,26 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "ShiggyXposed.xposed"
|
namespace = "GoonXposed.xposed"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "ShiggyXposed.xposed"
|
applicationId = "GoonXposed.xposed"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1350
|
versionCode = 1358
|
||||||
versionName = "1.3.5"
|
versionName = "1.3.8"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
create("release") {
|
create("release") {
|
||||||
storeFile = file("../release.keystore")
|
val keystoreFile = file("../release.keystore")
|
||||||
storePassword = "shiggyxposed"
|
if (keystoreFile.exists()) {
|
||||||
keyAlias = "shiggyxposed"
|
storeFile = keystoreFile
|
||||||
keyPassword = "shiggyxposed"
|
storePassword = "goonxposed"
|
||||||
|
keyAlias = "goonxposed"
|
||||||
|
keyPassword = "goonxposed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +37,10 @@ android {
|
||||||
release {
|
release {
|
||||||
isDebuggable = false
|
isDebuggable = false
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
signingConfig = signingConfigs.getByName("release")
|
val keystoreFile = file("../release.keystore")
|
||||||
|
if (keystoreFile.exists()) {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
@ -62,5 +68,6 @@ dependencies {
|
||||||
implementation("io.ktor:ktor-client-core:3.3.1")
|
implementation("io.ktor:ktor-client-core:3.3.1")
|
||||||
implementation("io.ktor:ktor-client-cio:3.3.1")
|
implementation("io.ktor:ktor-client-cio:3.3.1")
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
implementation("com.google.android.material:material:1.12.0")
|
||||||
implementation("dev.rushii:libunbound:1.0.0")
|
implementation("dev.rikka.shizuku:api:13.1.5")
|
||||||
|
implementation("dev.rikka.shizuku:provider:13.1.5")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<meta-data android:name="xposedmodule" android:value="true" />
|
<meta-data android:name="xposedmodule" android:value="true" />
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="xposeddescription"
|
android:name="xposeddescription"
|
||||||
android:value="Xposed module to inject ShiggyCord into discord"
|
android:value="Xposed module to inject GoonCord into discord"
|
||||||
/>
|
/>
|
||||||
<meta-data android:name="xposedminversion" android:value="82" />
|
<meta-data android:name="xposedminversion" android:value="82" />
|
||||||
<meta-data android:name="xposedscope" android:value="com.discord" />
|
<meta-data android:name="xposedscope" android:value="com.discord" />
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
ShiggyXposed.xposed.Main
|
GoonXposed.xposed.Main
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed
|
package GoonXposed.xposed
|
||||||
|
|
||||||
class Constants {
|
class Constants {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
@ -10,10 +10,10 @@ class Constants {
|
||||||
const val MAIN_SCRIPT_FILE = "bundle.js"
|
const val MAIN_SCRIPT_FILE = "bundle.js"
|
||||||
|
|
||||||
|
|
||||||
const val LOG_TAG = "Shiggy"
|
const val LOG_TAG = "Goon"
|
||||||
|
|
||||||
const val LOADER_NAME = "ShiggyXposed"
|
const val LOADER_NAME = "GoonXposed"
|
||||||
|
|
||||||
const val USER_AGENT = "ShiggyXposed"
|
const val USER_AGENT = "GoonXposed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed
|
package GoonXposed.xposed
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -7,16 +7,15 @@ import android.os.Bundle
|
||||||
import de.robv.android.xposed.IXposedHookLoadPackage
|
import de.robv.android.xposed.IXposedHookLoadPackage
|
||||||
import de.robv.android.xposed.IXposedHookZygoteInit
|
import de.robv.android.xposed.IXposedHookZygoteInit
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import ShiggyXposed.xposed.modules.*
|
import GoonXposed.xposed.modules.*
|
||||||
import ShiggyXposed.xposed.modules.appearance.FontsModule
|
import GoonXposed.xposed.modules.appearance.FontsModule
|
||||||
import ShiggyXposed.xposed.modules.appearance.SysColorsModule
|
import GoonXposed.xposed.modules.appearance.SysColorsModule
|
||||||
import ShiggyXposed.xposed.modules.appearance.ThemesModule
|
import GoonXposed.xposed.modules.appearance.ThemesModule
|
||||||
import ShiggyXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule
|
import GoonXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule
|
||||||
import ShiggyXposed.xposed.modules.bridge.BridgeModule
|
import GoonXposed.xposed.modules.bridge.BridgeModule
|
||||||
import ShiggyXposed.xposed.modules.no_track.BlockCrashReportingModule
|
import GoonXposed.xposed.modules.no_track.BlockCrashReportingModule
|
||||||
import ShiggyXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
|
import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
|
||||||
import ShiggyXposed.xposed.modules.LogBox.*
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
|
||||||
object HookStateHolder {
|
object HookStateHolder {
|
||||||
|
|
|
||||||
|
|
@ -1,183 +1,161 @@
|
||||||
package ShiggyXposed.xposed
|
package GoonXposed.xposed
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.content.pm.PackageManager
|
||||||
import de.robv.android.xposed.IXposedHookZygoteInit
|
import de.robv.android.xposed.IXposedHookZygoteInit
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
import de.robv.android.xposed.XC_MethodHook
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import kotlinx.serialization.json.JsonObjectBuilder
|
import kotlinx.serialization.json.JsonObjectBuilder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for every Xposed side module.
|
||||||
|
*
|
||||||
|
* The [Main] loader forwards the various [XC_LoadPackage] lifecycle callbacks to every
|
||||||
|
* registered [Module] instance. Modules may override any subset of the hooks below.
|
||||||
|
*/
|
||||||
|
open class Module {
|
||||||
|
open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {}
|
||||||
|
|
||||||
|
open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {}
|
||||||
|
|
||||||
|
open fun onContext(context: Context) {}
|
||||||
|
|
||||||
|
open fun onActivity(activity: Activity) {}
|
||||||
|
|
||||||
|
@Deprecated("This method is deprecated in the parent class")
|
||||||
|
open fun buildPayload(builder: JsonObjectBuilder) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runtime wrapper around an [XC_MethodHook.MethodHookParam] used as the receiver of every
|
||||||
|
* `before`/`after` hook block, exposing [thisObject], [args], [result] and [param] directly.
|
||||||
|
*/
|
||||||
|
class HookScope(val param: XC_MethodHook.MethodHookParam) {
|
||||||
|
val thisObject: Any?
|
||||||
|
get() = param.thisObject
|
||||||
|
|
||||||
|
val args: Array<Any?>
|
||||||
|
get() = param.args
|
||||||
|
|
||||||
|
var result: Any?
|
||||||
|
get() = param.result
|
||||||
|
set(value) {
|
||||||
|
param.result = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var throwable: Throwable?
|
||||||
|
get() = param.throwable
|
||||||
|
set(value) {
|
||||||
|
param.throwable = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DSL builder for lazily constructed [XC_MethodHook] instances.
|
||||||
|
*
|
||||||
|
* ```kotlin
|
||||||
|
* val hook = MethodHookBuilder().run {
|
||||||
|
* before { result = null }
|
||||||
|
* after { Log.i("done") }
|
||||||
|
* build()
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class MethodHookBuilder {
|
||||||
|
private val beforeCallbacks = mutableListOf<HookScope.() -> Unit>()
|
||||||
|
private val afterCallbacks = mutableListOf<HookScope.() -> Unit>()
|
||||||
|
|
||||||
|
fun before(callback: HookScope.() -> Unit) {
|
||||||
|
beforeCallbacks += callback
|
||||||
|
}
|
||||||
|
|
||||||
|
fun after(callback: HookScope.() -> Unit) {
|
||||||
|
afterCallbacks += callback
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): XC_MethodHook = object : XC_MethodHook() {
|
||||||
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
val scope = HookScope(param)
|
||||||
|
beforeCallbacks.forEach { scope.run(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterHookedMethod(param: MethodHookParam) {
|
||||||
|
val scope = HookScope(param)
|
||||||
|
afterCallbacks.forEach { scope.run(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun from(block: MethodHookBuilder.() -> Unit): XC_MethodHook = MethodHookBuilder().run {
|
||||||
|
block()
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ClassLoader.safeLoadClass(name: String): Class<*>? = try {
|
||||||
|
loadClass(name)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Class<*>.method(name: String, vararg parameterTypes: Class<*>): Method =
|
||||||
|
XposedHelpers.findMethodExact(this, name, *parameterTypes)
|
||||||
|
|
||||||
|
fun Method.hook(block: MethodHookBuilder.() -> Unit) {
|
||||||
|
val builder = MethodHookBuilder()
|
||||||
|
builder.block()
|
||||||
|
XposedBridge.hookMethod(this, builder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Method.hook(hook: XC_MethodHook) {
|
||||||
|
XposedBridge.hookMethod(this, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Class<*>.hookMethod(
|
||||||
|
name: String,
|
||||||
|
vararg parameterTypes: Class<*>,
|
||||||
|
block: MethodHookBuilder.() -> Unit
|
||||||
|
): Method {
|
||||||
|
val method = method(name, *parameterTypes)
|
||||||
|
method.hook(block)
|
||||||
|
return method
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.asDir(): File {
|
||||||
|
if (!exists() || !isDirectory) mkdirs()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.asFile(): File {
|
||||||
|
parentFile?.mkdirs()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
data class AppInfo(
|
data class AppInfo(
|
||||||
val name: String,
|
val name: String,
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val versionCode: Long,
|
val versionCode: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
abstract class Module {
|
fun Context.getAppInfo(): AppInfo {
|
||||||
/**
|
val label = applicationInfo.loadLabel(packageManager).toString()
|
||||||
* Builds a JSON payload to be injected into the JavaScript context.
|
val packageInfo = try {
|
||||||
*/
|
packageManager.getPackageInfo(packageName, 0)
|
||||||
@Deprecated("This will be removed in future versions. Payloads can be replaced via synchronous bridge methods.")
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
open fun buildPayload(builder: JsonObjectBuilder) {
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return AppInfo(
|
||||||
* Called during Zygote initialization.
|
name = label,
|
||||||
*/
|
packageName = packageName,
|
||||||
open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {}
|
version = packageInfo?.versionName ?: "",
|
||||||
|
versionCode = packageInfo?.versionCode ?: 0
|
||||||
/**
|
)
|
||||||
* Called when a package is loaded.
|
|
||||||
*
|
|
||||||
* Hooks should typically be set up here.
|
|
||||||
*
|
|
||||||
* Bridge methods that do not require [Context] can also be registered here.
|
|
||||||
*/
|
|
||||||
open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after [Context] has been attached to a [android.content.ContextWrapper]
|
|
||||||
* via [android.content.ContextWrapper.attachBaseContext].
|
|
||||||
*
|
|
||||||
* This may be called multiple times for different [Context]s, e.g. when the app is killed by the system.
|
|
||||||
*
|
|
||||||
* For bridge methods that require access to [Context], register them here.
|
|
||||||
*/
|
|
||||||
open fun onContext(context: Context) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after an [Activity.onCreate] method is executed.
|
|
||||||
*
|
|
||||||
* UI code that requires an [Activity] context can be placed here.
|
|
||||||
*
|
|
||||||
* Bridge methods must not be registered here, as it can be too late and cause crashes.
|
|
||||||
*
|
|
||||||
* If you need to register bridge methods that require an [Activity], do it in [onContext] and make it async.
|
|
||||||
*
|
|
||||||
* Once an [Activity] is available, execute the queued calls. Then from JS, call the method asynchronously.
|
|
||||||
*/
|
|
||||||
open fun onActivity(activity: Activity) {}
|
|
||||||
|
|
||||||
protected fun Context.getAppInfo(): AppInfo {
|
|
||||||
val pInfo = packageManager.getPackageInfo(packageName, 0)
|
|
||||||
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) pInfo.longVersionCode
|
|
||||||
else @Suppress("DEPRECATION") pInfo.versionCode.toLong()
|
|
||||||
|
|
||||||
return AppInfo(
|
|
||||||
packageManager.getApplicationLabel(applicationInfo).toString(),
|
|
||||||
packageName,
|
|
||||||
pInfo.versionName ?: versionCode.toString(),
|
|
||||||
versionCode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun File.asDir() {
|
|
||||||
if (!this.isDirectory) this.delete()
|
|
||||||
this.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun File.asFile() {
|
|
||||||
if (!this.isFile) this.deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun Class<*>.method(
|
|
||||||
name: String, vararg params: Class<*>?
|
|
||||||
): Method = getDeclaredMethod(name, *params).apply {
|
|
||||||
isAccessible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun ClassLoader.safeLoadClass(name: String): Class<*>? = runCatching { loadClass(name) }.getOrNull()
|
|
||||||
|
|
||||||
protected fun Method.hook(hook: XC_MethodHook): XC_MethodHook.Unhook = XposedBridge.hookMethod(this, hook)
|
|
||||||
|
|
||||||
protected fun Method.hook(block: MethodHookBuilder.() -> Unit): XC_MethodHook.Unhook =
|
|
||||||
hook(MethodHookBuilder().apply(block).build())
|
|
||||||
|
|
||||||
protected fun Class<*>.hookMethod(
|
|
||||||
name: String, vararg params: Class<*>?, block: MethodHookBuilder.() -> Unit
|
|
||||||
): XC_MethodHook.Unhook = method(name, *params).hook(MethodHookBuilder().apply(block).build())
|
|
||||||
|
|
||||||
protected class MethodHookBuilder {
|
|
||||||
private var beforeBlock: (HookScope.() -> Unit)? = null
|
|
||||||
private var afterBlock: (HookScope.() -> Unit)? = null
|
|
||||||
|
|
||||||
fun before(block: HookScope.() -> Unit) {
|
|
||||||
beforeBlock = block
|
|
||||||
}
|
|
||||||
|
|
||||||
fun after(block: HookScope.() -> Unit) {
|
|
||||||
afterBlock = block
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(): XC_MethodHook = object : XC_MethodHook() {
|
|
||||||
override fun beforeHookedMethod(param: MethodHookParam) {
|
|
||||||
val b = beforeBlock
|
|
||||||
if (b != null) {
|
|
||||||
val scope = HookScope(
|
|
||||||
param = param, proceed = { p ->
|
|
||||||
super.beforeHookedMethod(p)
|
|
||||||
})
|
|
||||||
scope.b()
|
|
||||||
} else {
|
|
||||||
super.beforeHookedMethod(param)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterHookedMethod(param: MethodHookParam) {
|
|
||||||
val a = afterBlock
|
|
||||||
if (a != null) {
|
|
||||||
val scope = HookScope(
|
|
||||||
param = param, proceed = { p ->
|
|
||||||
super.afterHookedMethod(p)
|
|
||||||
})
|
|
||||||
scope.a()
|
|
||||||
} else {
|
|
||||||
super.afterHookedMethod(param)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope object passed to before/after hook blocks.
|
|
||||||
*
|
|
||||||
* Provides:
|
|
||||||
* - Access to the [param] object
|
|
||||||
* - [proceed] to call the original XC_MethodHook super method
|
|
||||||
* - Accessors for `thisObject`, `args`, `result`, and `throwable`
|
|
||||||
*
|
|
||||||
* @property param The [XC_MethodHook.MethodHookParam] for the current hook.
|
|
||||||
* @property proceed Function that calls the super method.
|
|
||||||
*/
|
|
||||||
protected class HookScope internal constructor(
|
|
||||||
val param: XC_MethodHook.MethodHookParam, private val proceed: (XC_MethodHook.MethodHookParam) -> Unit
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Continues with the default XC_MethodHook super behavior.
|
|
||||||
* Equivalent to calling `super.beforeHookedMethod(param)` or
|
|
||||||
* `super.afterHookedMethod(param)` depending on the phase.
|
|
||||||
*/
|
|
||||||
fun proceed() = proceed(param)
|
|
||||||
|
|
||||||
val thisObject: Any? get() = param.thisObject
|
|
||||||
|
|
||||||
val args: Array<Any?> get() = param.args
|
|
||||||
|
|
||||||
var result: Any?
|
|
||||||
get() = param.result
|
|
||||||
set(value) {
|
|
||||||
param.result = value
|
|
||||||
}
|
|
||||||
|
|
||||||
var throwable: Throwable?
|
|
||||||
get() = param.throwable
|
|
||||||
set(value) {
|
|
||||||
param.throwable = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package ShiggyXposed.xposed
|
package GoonXposed.xposed
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.app.AndroidAppHelper
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import ShiggyXposed.xposed.modules.UpdaterModule
|
import de.robv.android.xposed.XposedHelpers
|
||||||
|
import GoonXposed.xposed.modules.UpdaterModule
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
@ -14,14 +15,16 @@ class Utils {
|
||||||
val JSON = Json { ignoreUnknownKeys = true }
|
val JSON = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
fun reloadApp() {
|
fun reloadApp() {
|
||||||
val application = AndroidAppHelper.currentApplication()
|
val activityThread = XposedHelpers.findClass("android.app.ActivityThread", null)
|
||||||
|
val application =
|
||||||
|
XposedHelpers.callStaticMethod(activityThread, "currentApplication") as? Application ?: return
|
||||||
val intent = application.packageManager.getLaunchIntentForPackage(application.packageName)
|
val intent = application.packageManager.getLaunchIntentForPackage(application.packageName)
|
||||||
application.startActivity(Intent.makeRestartActivityTask(intent!!.component))
|
application.startActivity(Intent.makeRestartActivityTask(intent!!.component))
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showRecoveryAlert(context: Context) {
|
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 ->
|
.setItems(arrayOf("Reload", "Delete Script", "Reset Loader Config")) { _, which ->
|
||||||
when (which) {
|
when (which) {
|
||||||
0 -> {
|
0 -> {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AtomicFile
|
import android.util.AtomicFile
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import ShiggyXposed.xposed.modules.bridge.BridgeModule
|
import GoonXposed.xposed.asDir
|
||||||
|
import GoonXposed.xposed.asFile
|
||||||
|
import GoonXposed.xposed.getAppInfo
|
||||||
|
import GoonXposed.xposed.modules.bridge.BridgeModule
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,7 +38,7 @@ import java.io.*
|
||||||
*/
|
*/
|
||||||
object CacheModule : Module() {
|
object CacheModule : Module() {
|
||||||
|
|
||||||
private const val CACHE_DIR = "Shiggy"
|
private const val CACHE_DIR = "Goon"
|
||||||
|
|
||||||
private const val MODULES_CACHE_PREFIX = "modules"
|
private const val MODULES_CACHE_PREFIX = "modules"
|
||||||
private const val ASSETS_CACHE_PREFIX = "assets"
|
private const val ASSETS_CACHE_PREFIX = "assets"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
|
import GoonXposed.xposed.hookMethod
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Constants.Companion.TARGET_PACKAGE
|
import GoonXposed.xposed.Constants.Companion.TARGET_PACKAGE
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
|
import GoonXposed.xposed.hookMethod
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks [Resources.getIdentifier] to fix resource package name mismatch.
|
* Hooks [Resources.getIdentifier] to fix resource package name mismatch.
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import android.content.res.XModuleResources
|
import android.content.res.XModuleResources
|
||||||
import de.robv.android.xposed.IXposedHookZygoteInit
|
import de.robv.android.xposed.IXposedHookZygoteInit
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.HookStateHolder
|
import GoonXposed.xposed.HookScope
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.HookStateHolder
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
|
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.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
@ -54,11 +60,11 @@ object HookScriptLoaderModule : Module() {
|
||||||
|
|
||||||
private fun hook(instance: Class<*>) = runCatching {
|
private fun hook(instance: Class<*>) = runCatching {
|
||||||
val loadScriptFromAssets = instance.method(
|
val loadScriptFromAssets = instance.method(
|
||||||
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType
|
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
|
||||||
)
|
)
|
||||||
|
|
||||||
val loadScriptFromFile = instance.method(
|
val loadScriptFromFile = instance.method(
|
||||||
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
|
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
|
||||||
)
|
)
|
||||||
|
|
||||||
loadScriptFromAssets.hook {
|
loadScriptFromAssets.hook {
|
||||||
|
|
@ -83,17 +89,14 @@ object HookScriptLoaderModule : Module() {
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val ready = async { HookStateHolder.readyDeferred.join() }
|
val ready = async { HookStateHolder.readyDeferred.join() }
|
||||||
val isCustomUrl = UpdaterModule.isCustomUrlEnabled
|
|
||||||
|
|
||||||
if (!mainScript.exists() || isCustomUrl) {
|
if (!mainScript.exists()) {
|
||||||
val reason = if (isCustomUrl) "Custom URL enabled" else "Main script does not exist"
|
Log.i("Main script does not exist, downloading before load...")
|
||||||
Log.i("$reason, downloading before load...")
|
val download = async { UpdaterModule.downloadScript().join() }
|
||||||
val download =
|
|
||||||
async { UpdaterModule.downloadScript(showUpdateDialog = false).join() }
|
|
||||||
awaitAll(ready, download)
|
awaitAll(ready, download)
|
||||||
} else {
|
} else {
|
||||||
Log.i("Main script exists, updating in background...")
|
Log.i("Main script exists, updating in background...")
|
||||||
UpdaterModule.downloadScript(showUpdateDialog = true)
|
UpdaterModule.downloadScript()
|
||||||
ready.await()
|
ready.await()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,14 +113,10 @@ object HookScriptLoaderModule : Module() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If a disabled marker exists next to the cached bundle, treat this as a global
|
|
||||||
// disable for the entire script-loading step. In that case, we skip preloads,
|
|
||||||
// cached main script and the assets fallback so nothing is injected.
|
|
||||||
val disabledMarker = File(mainScript.parentFile, "${Constants.MAIN_SCRIPT_FILE}.disabled")
|
val disabledMarker = File(mainScript.parentFile, "${Constants.MAIN_SCRIPT_FILE}.disabled")
|
||||||
if (disabledMarker.exists()) {
|
if (disabledMarker.exists()) {
|
||||||
Log.i("Script loading disabled by marker; skipping preloads, cached bundle and fallback")
|
Log.i("Script loading disabled by marker; skipping preloads, cached bundle and fallback")
|
||||||
} else {
|
} else {
|
||||||
// Normal behaviour: run preloads then cached main script or fallback to bundled asset
|
|
||||||
preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile)
|
preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile)
|
||||||
|
|
||||||
if (mainScript.exists()) {
|
if (mainScript.exists()) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -62,7 +62,7 @@ object CustomBundleDialog {
|
||||||
container.addView(toggleContainer)
|
container.addView(toggleContainer)
|
||||||
|
|
||||||
val urlInput = EditText(context).apply {
|
val urlInput = EditText(context).apply {
|
||||||
hint = "http://localhost:4040/shiggycord.js"
|
hint = "http://localhost:4040/gooncord.js"
|
||||||
setTextColor(colors.onSurface)
|
setTextColor(colors.onSurface)
|
||||||
setHintTextColor(colors.onSurfaceVariant)
|
setHintTextColor(colors.onSurfaceVariant)
|
||||||
background = LogBoxUtils.createM3Background(context, colors.surfaceVariant, 12f)
|
background = LogBoxUtils.createM3Background(context, colors.surfaceVariant, 12f)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import ShiggyXposed.xposed.Utils.Companion.reloadApp
|
import GoonXposed.xposed.Utils.Companion.reloadApp
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
object LogBoxActions {
|
object LogBoxActions {
|
||||||
|
|
@ -134,7 +134,7 @@ object LogBoxActions {
|
||||||
val config = JSONObject(configFile.readText())
|
val config = JSONObject(configFile.readText())
|
||||||
val customLoadUrl = config.optJSONObject("customLoadUrl") ?: JSONObject()
|
val customLoadUrl = config.optJSONObject("customLoadUrl") ?: JSONObject()
|
||||||
customLoadUrl.put("enabled", false)
|
customLoadUrl.put("enabled", false)
|
||||||
customLoadUrl.put("url", "http://localhost:4040/shiggycord.js")
|
customLoadUrl.put("url", "http://localhost:4040/gooncord.js")
|
||||||
config.put("customLoadUrl", customLoadUrl)
|
config.put("customLoadUrl", customLoadUrl)
|
||||||
configFile.writeText(config.toString())
|
configFile.writeText(config.toString())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
|
||||||
object LogBoxDialogs {
|
object LogBoxDialogs {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -14,8 +14,8 @@ import android.content.res.ColorStateList
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import ShiggyXposed.xposed.Utils
|
import GoonXposed.xposed.Utils
|
||||||
|
|
||||||
object LogBoxMenu {
|
object LogBoxMenu {
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ object LogBoxMenu {
|
||||||
val container = createMenuContainer(context, colors)
|
val container = createMenuContainer(context, colors)
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
container.addView(createTitle(context, colors, "ShiggyXposed Recovery", center = true))
|
container.addView(createTitle(context, colors, "GoonXposed Recovery", center = true))
|
||||||
|
|
||||||
// Subtitle
|
// Subtitle
|
||||||
container.addView(createSubtitle(context, colors, "Select an option to continue"))
|
container.addView(createSubtitle(context, colors, "Select an option to continue"))
|
||||||
|
|
@ -782,7 +782,7 @@ object LogBoxMenu {
|
||||||
) { LogBoxActions.refetchBundle(context) }
|
) { LogBoxActions.refetchBundle(context) }
|
||||||
|
|
||||||
2 -> showCustomBundleDialog(context)
|
2 -> showCustomBundleDialog(context)
|
||||||
3 -> ShiggyXposed.xposed.Utils.Companion.reloadApp()
|
3 -> Utils.reloadApp()
|
||||||
4 -> showConfirmAction(
|
4 -> showConfirmAction(
|
||||||
context, "Clear Cache & Reset",
|
context, "Clear Cache & Reset",
|
||||||
"This will clear all cached bundles and reset to default settings."
|
"This will clear all cached bundles and reset to default settings."
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
import GoonXposed.xposed.Utils.Companion.reloadApp
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import de.robv.android.xposed.XC_MethodHook
|
||||||
import de.robv.android.xposed.XC_MethodReplacement
|
import de.robv.android.xposed.XC_MethodReplacement
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
|
|
@ -22,11 +24,15 @@ object LogBoxModule : Module() {
|
||||||
val getUseDeveloperSupportMethod =
|
val getUseDeveloperSupportMethod =
|
||||||
dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" }
|
dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" }
|
||||||
|
|
||||||
getUseDeveloperSupportMethod.hook {
|
// NOTE: This used to call a custom `.hook { before { result = true } }` DSL that
|
||||||
before {
|
// relied on a shared extensions file which broke (unresolved references).
|
||||||
result = true
|
// Rewritten using the standard XposedBridge/XC_MethodHook API, which is already
|
||||||
|
// used elsewhere in this file, to force this method to always return true.
|
||||||
|
XposedBridge.hookMethod(getUseDeveloperSupportMethod, object : XC_MethodHook() {
|
||||||
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
param.result = true
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
Log.e("Successfully hooked DCDReactNativeHost")
|
Log.e("Successfully hooked DCDReactNativeHost")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Failed to hook DCDReactNativeHost: ${e.message}")
|
Log.e("Failed to hook DCDReactNativeHost: ${e.message}")
|
||||||
|
|
@ -95,7 +101,7 @@ object LogBoxModule : Module() {
|
||||||
XposedBridge.hookMethod(handleReloadJSMethod, object : XC_MethodReplacement() {
|
XposedBridge.hookMethod(handleReloadJSMethod, object : XC_MethodReplacement() {
|
||||||
override fun replaceHookedMethod(param: MethodHookParam): Any? {
|
override fun replaceHookedMethod(param: MethodHookParam): Any? {
|
||||||
Log.e("handleReloadJS called - reloading app")
|
Log.e("handleReloadJS called - reloading app")
|
||||||
ShiggyXposed.xposed.Utils.Companion.reloadApp()
|
reloadApp()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
|
||||||
object LogBoxTheme {
|
object LogBoxTheme {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
|
@ -9,8 +9,8 @@ import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
|
||||||
object LogBoxUtils {
|
object LogBoxUtils {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -19,40 +19,29 @@ object OptionsMenu {
|
||||||
container.addView(LogBoxComponents.createM3Button(context, injectionText, colors) {
|
container.addView(LogBoxComponents.createM3Button(context, injectionText, colors) {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
LogBoxActions.toggleBundleInjection(context)
|
LogBoxActions.toggleBundleInjection(context)
|
||||||
|
GoonXposed.xposed.Utils.Companion.reloadApp()
|
||||||
})
|
})
|
||||||
|
|
||||||
container.addView(LogBoxComponents.createM3Button(context, "Refetch Bundle", colors) {
|
container.addView(LogBoxComponents.createM3Button(context, "Refetch Bundle", colors) {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
RecoveryMenu::class.java.getDeclaredMethod(
|
RecoveryMenu.showConfirmAction(
|
||||||
"showConfirmAction",
|
|
||||||
Context::class.java,
|
|
||||||
String::class.java,
|
|
||||||
String::class.java,
|
|
||||||
Runnable::class.java
|
|
||||||
).apply { isAccessible = true }.invoke(
|
|
||||||
null,
|
|
||||||
context,
|
context,
|
||||||
"Refetch Bundle",
|
"Refetch Bundle",
|
||||||
"This will download the latest bundle from Github.",
|
"This will download the latest bundle from Github."
|
||||||
Runnable { LogBoxActions.refetchBundle(context) }
|
) {
|
||||||
)
|
LogBoxActions.refetchBundle(context)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
container.addView(LogBoxComponents.createM3Button(context, "Clear Cache & Reset", colors) {
|
container.addView(LogBoxComponents.createM3Button(context, "Clear Cache & Reset", colors) {
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
RecoveryMenu::class.java.getDeclaredMethod(
|
RecoveryMenu.showConfirmAction(
|
||||||
"showConfirmAction",
|
|
||||||
Context::class.java,
|
|
||||||
String::class.java,
|
|
||||||
String::class.java,
|
|
||||||
Runnable::class.java
|
|
||||||
).apply { isAccessible = true }.invoke(
|
|
||||||
null,
|
|
||||||
context,
|
context,
|
||||||
"Clear Cache & Reset",
|
"Clear Cache & Reset",
|
||||||
"This will clear all cached bundles and reset to default settings.",
|
"This will clear all cached bundles and reset to default settings."
|
||||||
Runnable { LogBoxActions.clearCacheAndReset(context) }
|
) {
|
||||||
)
|
LogBoxActions.clearCacheAndReset(context)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
container.addView(LogBoxComponents.createM3Button(context, "Back", colors) {
|
container.addView(LogBoxComponents.createM3Button(context, "Back", colors) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package ShiggyXposed.xposed.modules.LogBox
|
package GoonXposed.xposed.modules.LogBox
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
|
||||||
object RecoveryMenu {
|
object RecoveryMenu {
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ object RecoveryMenu {
|
||||||
|
|
||||||
val container = LogBoxComponents.createMenuContainer(context, colors)
|
val container = LogBoxComponents.createMenuContainer(context, colors)
|
||||||
|
|
||||||
container.addView(LogBoxComponents.createTitle(context, colors, "ShiggyXposed Recovery", center = true))
|
container.addView(LogBoxComponents.createTitle(context, colors, "GoonXposed Recovery", center = true))
|
||||||
container.addView(LogBoxComponents.createSubtitle(context, colors, "Select an option to continue"))
|
container.addView(LogBoxComponents.createSubtitle(context, colors, "Select an option to continue"))
|
||||||
|
|
||||||
val safeModeText = if (LogBoxUtils.isSafeModeEnabled(context)) "Disable Safe Mode" else "Enable Safe Mode"
|
val safeModeText = if (LogBoxUtils.isSafeModeEnabled(context)) "Disable Safe Mode" else "Enable Safe Mode"
|
||||||
|
|
@ -63,7 +63,7 @@ object RecoveryMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> CustomBundleDialog.showCustomBundleDialog(context)
|
2 -> CustomBundleDialog.showCustomBundleDialog(context)
|
||||||
3 -> ShiggyXposed.xposed.Utils.Companion.reloadApp()
|
3 -> GoonXposed.xposed.Utils.Companion.reloadApp()
|
||||||
4 -> showConfirmAction(
|
4 -> showConfirmAction(
|
||||||
context,
|
context,
|
||||||
"Clear Cache & Reset",
|
"Clear Cache & Reset",
|
||||||
|
|
@ -76,7 +76,7 @@ object RecoveryMenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showConfirmAction(context: Context, title: String, message: String, action: () -> Unit) {
|
fun showConfirmAction(context: Context, title: String, message: String, action: () -> Unit) {
|
||||||
val colors = LogBoxTheme.getM3Colors(context)
|
val colors = LogBoxTheme.getM3Colors(context)
|
||||||
val container = LogBoxComponents.createMenuContainer(context, colors)
|
val container = LogBoxComponents.createMenuContainer(context, colors)
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,18 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
import de.robv.android.xposed.XC_MethodHook
|
||||||
import de.robv.android.xposed.XposedBridge
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.BuildConfig
|
import GoonXposed.xposed.BuildConfig
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.MethodHookBuilder
|
||||||
import ShiggyXposed.xposed.Utils.Companion.JSON
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
|
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.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -48,7 +52,7 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
|
||||||
"loadScriptFromFile",
|
"loadScriptFromFile",
|
||||||
String::class.java,
|
String::class.java,
|
||||||
String::class.java,
|
String::class.java,
|
||||||
Boolean::class.javaPrimitiveType
|
Boolean::class.javaPrimitiveType!!
|
||||||
), param.thisObject, arrayOf(absolutePath, absolutePath, param.args[2])
|
), param.thisObject, arrayOf(absolutePath, absolutePath, param.args[2])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -70,11 +74,11 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
|
||||||
|
|
||||||
private fun hook(instance: Class<*>, hook: XC_MethodHook) {
|
private fun hook(instance: Class<*>, hook: XC_MethodHook) {
|
||||||
instance.method(
|
instance.method(
|
||||||
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType,
|
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType!!,
|
||||||
).hook(hook)
|
).hook(hook)
|
||||||
|
|
||||||
instance.method(
|
instance.method(
|
||||||
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
|
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
|
||||||
).hook(hook)
|
).hook(hook)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import kotlinx.serialization.json.JsonObjectBuilder
|
import kotlinx.serialization.json.JsonObjectBuilder
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AtomicFile
|
import android.util.AtomicFile
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import ShiggyXposed.xposed.modules.bridge.BridgeModule
|
import GoonXposed.xposed.asFile
|
||||||
|
import GoonXposed.xposed.modules.bridge.BridgeModule
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
package ShiggyXposed.xposed.modules
|
package GoonXposed.xposed.modules
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AtomicFile
|
import android.util.AtomicFile
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.util.writeBytes
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import dev.rushii.libunbound.LibUnbound
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Utils
|
||||||
import ShiggyXposed.xposed.Utils
|
import GoonXposed.xposed.Utils.Companion.JSON
|
||||||
import ShiggyXposed.xposed.Utils.Companion.JSON
|
import GoonXposed.xposed.Utils.Companion.reloadApp
|
||||||
|
import GoonXposed.xposed.Utils.Log
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
|
||||||
import ShiggyXposed.xposed.modules.bridge.BridgeModule
|
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
|
|
@ -22,41 +22,46 @@ import io.ktor.client.statement.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CustomLoadUrl(val enabled: Boolean = false, val url: String = "")
|
data class CustomLoadUrl(
|
||||||
|
val enabled: Boolean = false, val url: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LoaderConfig(val customLoadUrl: CustomLoadUrl = CustomLoadUrl(), val disableInjection: Boolean = false)
|
data class LoaderConfig(
|
||||||
|
val customLoadUrl: CustomLoadUrl = CustomLoadUrl()
|
||||||
@Serializable
|
)
|
||||||
data class EndpointInfo(val paths: ArrayList<String>, val hash: String? = null, val version: String)
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module that updates the JS bundle by downloading it from a remote URL.
|
||||||
|
*
|
||||||
|
* Shows dialogs when failed allowing retry.
|
||||||
|
*/
|
||||||
object UpdaterModule : Module() {
|
object UpdaterModule : Module() {
|
||||||
private lateinit var config: LoaderConfig
|
private lateinit var config: LoaderConfig
|
||||||
val isCustomUrlEnabled: Boolean
|
|
||||||
get() = config.customLoadUrl.enabled
|
|
||||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
private var lastActivity: WeakReference<Activity>? = null
|
private var lastActivity: WeakReference<Activity>? = null
|
||||||
|
|
||||||
private lateinit var cacheDir: File
|
private lateinit var cacheDir: File
|
||||||
private lateinit var bundle: File
|
private lateinit var bundle: File
|
||||||
private lateinit var etag: File
|
private lateinit var etag: File
|
||||||
|
// Path to the application's data directory so we can read LogBox settings
|
||||||
|
private lateinit var appDataDir: File
|
||||||
|
|
||||||
private const val TIMEOUT_STRICT = 15000L
|
private const val TIMEOUT_CACHED = 5000L
|
||||||
private const val MIN_BYTECODE_SIZE = 512
|
private const val TIMEOUT = 10000L
|
||||||
private const val ETAG_FILE = "etag.txt"
|
private const val ETAG_FILE = "etag.txt"
|
||||||
private const val CONFIG_FILE = "loader.json"
|
private const val CONFIG_FILE = "loader.json"
|
||||||
|
|
||||||
private const val DEFAULT_BASE_URL = "https://github.com/kmmiio99o/ShiggyCord/releases/latest/download/"
|
private const val DEFAULT_BUNDLE_URL =
|
||||||
private const val FALLBACK_BASE_URL = "https://bundle.shiggycord.dev/"
|
"https://github.com/OTKUSteyler/GoonCord/releases/latest/download/gooncord.js"
|
||||||
private const val DEFAULT_BUNDLE_NAME = "shiggycord.min.js"
|
|
||||||
|
|
||||||
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
||||||
|
// store app data dir for later checks (LogBox settings live under files/logbox)
|
||||||
|
appDataDir = File(appInfo.dataDir)
|
||||||
cacheDir = File(appInfo.dataDir, Constants.CACHE_DIR).apply { mkdirs() }
|
cacheDir = File(appInfo.dataDir, Constants.CACHE_DIR).apply { mkdirs() }
|
||||||
val filesDir = File(appInfo.dataDir, Constants.FILES_DIR).apply { mkdirs() }
|
val filesDir = File(appInfo.dataDir, Constants.FILES_DIR).apply { mkdirs() }
|
||||||
|
|
||||||
|
|
@ -64,100 +69,91 @@ object UpdaterModule : Module() {
|
||||||
etag = File(cacheDir, ETAG_FILE)
|
etag = File(cacheDir, ETAG_FILE)
|
||||||
|
|
||||||
val configFile = File(filesDir, CONFIG_FILE)
|
val configFile = File(filesDir, CONFIG_FILE)
|
||||||
config = runCatching {
|
|
||||||
if (configFile.exists()) JSON.decodeFromString<LoaderConfig>(configFile.readText()) else LoaderConfig()
|
|
||||||
}.getOrDefault(LoaderConfig())
|
|
||||||
|
|
||||||
BridgeModule.registerMethod("revenge.updater.clear") {
|
config = runCatching {
|
||||||
if (bundle.exists()) bundle.delete()
|
if (configFile.exists()) {
|
||||||
if (etag.exists()) etag.delete()
|
JSON.decodeFromString<LoaderConfig>(configFile.readText())
|
||||||
null
|
} else LoaderConfig()
|
||||||
}
|
}.getOrDefault(LoaderConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadScript(activity: Activity? = null, showUpdateDialog: Boolean = true): Job = scope.launch {
|
fun downloadScript(activity: Activity? = null): Job = scope.launch {
|
||||||
try {
|
try {
|
||||||
|
// Respect LogBox setting to disable bundle injections.
|
||||||
|
// If the file "files/logbox/LOGBOX_SETTINGS" contains the flag bundleInjectionDisabled=true,
|
||||||
|
// skip attempting to download/update the bundle.
|
||||||
|
try {
|
||||||
|
val settingsFile = File(appDataDir, "files/logbox/LOGBOX_SETTINGS")
|
||||||
|
if (settingsFile.exists()) {
|
||||||
|
val txt = settingsFile.readText()
|
||||||
|
// look for a simple true marker; this avoids introducing a new JSON dependency here
|
||||||
|
if (txt.contains("\"bundleInjectionDisabled\"") && txt.contains("true")) {
|
||||||
|
Log.i("UpdaterModule: bundle injection disabled by LogBox settings — skipping download")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ignore: Exception) {
|
||||||
|
// If anything goes wrong reading the setting, proceed with normal behaviour.
|
||||||
|
}
|
||||||
|
|
||||||
HttpClient(CIO) {
|
HttpClient(CIO) {
|
||||||
expectSuccess = false
|
expectSuccess = false
|
||||||
install(HttpTimeout) {
|
|
||||||
requestTimeoutMillis = TIMEOUT_STRICT
|
|
||||||
connectTimeoutMillis = 5000L
|
|
||||||
socketTimeoutMillis = TIMEOUT_STRICT
|
|
||||||
}
|
|
||||||
install(UserAgent) { agent = Constants.USER_AGENT }
|
install(UserAgent) { agent = Constants.USER_AGENT }
|
||||||
install(HttpRedirect) { checkHttpMethod = false }
|
install(HttpRedirect) {}
|
||||||
}.use { client ->
|
}.use { client ->
|
||||||
val targetUrl = resolveTargetUrl(client)
|
val url = config.customLoadUrl.takeIf { it.enabled }?.url ?: DEFAULT_BUNDLE_URL
|
||||||
Log.i("Fetching bundle: $targetUrl")
|
Log.i("Fetching JS bundle from: $url")
|
||||||
|
|
||||||
val response: HttpResponse = client.get(targetUrl) {
|
val response: HttpResponse = client.get(url) {
|
||||||
headers {
|
headers {
|
||||||
if (etag.exists() && bundle.exists()) {
|
if (etag.exists() && bundle.exists()) {
|
||||||
append(HttpHeaders.IfNoneMatch, etag.readText())
|
append(HttpHeaders.IfNoneMatch, etag.readText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retries don't need timeout
|
||||||
|
if (activity == null) {
|
||||||
|
timeout {
|
||||||
|
requestTimeoutMillis = if (!bundle.exists()) TIMEOUT else TIMEOUT_CACHED
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (response.status) {
|
when (response.status) {
|
||||||
HttpStatusCode.OK -> {
|
HttpStatusCode.OK -> {
|
||||||
val bytes: ByteArray = response.body()
|
val bytes: ByteArray = response.body()
|
||||||
|
AtomicFile(bundle).writeBytes(bytes)
|
||||||
|
|
||||||
if (bytes.size < MIN_BYTECODE_SIZE) {
|
val newTag = response.headers[HttpHeaders.ETag]
|
||||||
throw Exception("Payload too small (${bytes.size} bytes). Possible corrupt build.")
|
if (!newTag.isNullOrEmpty()) etag.writeText(newTag) else etag.delete()
|
||||||
}
|
|
||||||
|
|
||||||
AtomicFile(bundle).apply {
|
Log.i("Bundle updated (${bytes.size} bytes)")
|
||||||
val stream = startWrite()
|
|
||||||
try {
|
// This is a retry, so we show a dialog
|
||||||
stream.write(bytes)
|
if (activity != null) {
|
||||||
finishWrite(stream)
|
withContext(Dispatchers.Main) {
|
||||||
} catch (e: Exception) {
|
AlertDialog.Builder(activity).setTitle("GoonCord Update Successful")
|
||||||
failWrite(stream)
|
.setMessage("A reload is required for changes to take effect.")
|
||||||
throw e
|
.setPositiveButton("Reload") { dialog, _ ->
|
||||||
|
reloadApp()
|
||||||
|
dialog.dismiss()
|
||||||
|
}.setCancelable(false).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.headers[HttpHeaders.ETag]?.let { etag.writeText(it) } ?: etag.delete()
|
|
||||||
Log.i("Bundle updated: ${bytes.size} bytes")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpStatusCode.NotModified -> Log.i("Bundle is up to date (304)")
|
HttpStatusCode.NotModified -> {
|
||||||
else -> throw ResponseException(response, "HTTP ${response.status}")
|
Log.i("Server responded with 304, no changes")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
throw ResponseException(response, "Received status: ${response.status}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e("Updater Error", e)
|
Log.e("Failed to download script", e)
|
||||||
}
|
showErrorDialog(e)
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun resolveTargetUrl(client: HttpClient): String {
|
|
||||||
if (config.customLoadUrl.enabled && config.customLoadUrl.url.isNotEmpty()) {
|
|
||||||
return config.customLoadUrl.url
|
|
||||||
}
|
|
||||||
|
|
||||||
return tryResolveUrl(client, DEFAULT_BASE_URL)
|
|
||||||
?: tryResolveUrl(client, FALLBACK_BASE_URL)
|
|
||||||
?: DEFAULT_BASE_URL + DEFAULT_BUNDLE_NAME
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun tryResolveUrl(client: HttpClient, baseUrl: String): String? {
|
|
||||||
return try {
|
|
||||||
val infoResponse = client.get("${baseUrl}info.json")
|
|
||||||
if (infoResponse.status == HttpStatusCode.OK) {
|
|
||||||
val info = JSON.decodeFromString<EndpointInfo>(infoResponse.bodyAsText())
|
|
||||||
val hermesVersion = withTimeoutOrNull(2000L) {
|
|
||||||
runCatching { LibUnbound.getHermesRuntimeBytecodeVersion() }.getOrNull()
|
|
||||||
} ?: 96
|
|
||||||
|
|
||||||
val hbcName = "shiggycord.$hermesVersion.hbc"
|
|
||||||
when {
|
|
||||||
info.paths.contains(hbcName) -> baseUrl + hbcName
|
|
||||||
info.paths.contains("shiggycord.min.js") -> baseUrl + "shiggycord.min.js"
|
|
||||||
else -> baseUrl + DEFAULT_BUNDLE_NAME
|
|
||||||
}
|
|
||||||
} else null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,16 +161,16 @@ object UpdaterModule : Module() {
|
||||||
lastActivity = WeakReference(activity)
|
lastActivity = WeakReference(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDisableInjection(context: Context, disabled: Boolean) {
|
fun showErrorDialog(e: Throwable) {
|
||||||
val filesDir = File(context.dataDir, Constants.FILES_DIR).apply { mkdirs() }
|
val activity = lastActivity?.get() ?: return
|
||||||
val configFile = File(filesDir, CONFIG_FILE)
|
|
||||||
val newCfg = config.copy(disableInjection = disabled)
|
activity.runOnUiThread {
|
||||||
configFile.writeText(JSON.encodeToString(newCfg))
|
}
|
||||||
config = newCfg
|
|
||||||
Toast.makeText(context, "Injection ${if (disabled) "disabled" else "enabled"}", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInjectionDisabled(context: Context? = null): Boolean {
|
fun resetLoaderConfig(context: Context) {
|
||||||
return if (::config.isInitialized) config.disableInjection else false
|
val filesDir = File(context.dataDir, Constants.FILES_DIR)
|
||||||
|
val config = File(filesDir, CONFIG_FILE)
|
||||||
|
if (config.exists()) config.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
package ShiggyXposed.xposed.modules.appearance
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package GoonXposed.xposed.modules.appearance
|
||||||
|
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
|
@ -9,10 +11,10 @@ import android.os.Build
|
||||||
import de.robv.android.xposed.XC_MethodReplacement
|
import de.robv.android.xposed.XC_MethodReplacement
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Companion.JSON
|
import GoonXposed.xposed.Utils.Companion.JSON
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.engine.cio.*
|
import io.ktor.client.engine.cio.*
|
||||||
|
|
@ -27,6 +29,14 @@ import kotlinx.serialization.json.put
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
// NOTE: These extensions used to be resolved from a shared Utils/Extensions file,
|
||||||
|
// but that reference broke (renamed/removed/relocated), causing "Unresolved reference"
|
||||||
|
// build failures. Defining them locally here removes the dependency on that file.
|
||||||
|
// asFile(): ensures the parent directory of a file exists.
|
||||||
|
// asDir(): ensures the directory itself exists.
|
||||||
|
private fun File.asFile(): File = apply { parentFile?.mkdirs() }
|
||||||
|
private fun File.asDir(): File = apply { mkdirs() }
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FontDefinition(
|
data class FontDefinition(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
|
|
@ -93,28 +103,40 @@ object FontsModule : Module() {
|
||||||
|
|
||||||
// These files should be downloaded by the JS side, but oh well
|
// These files should be downloaded by the JS side, but oh well
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
fontDef.main.keys.map { name ->
|
val client = HttpClient(CIO) {
|
||||||
async {
|
install(UserAgent) { agent = Constants.USER_AGENT }
|
||||||
val url = fontDef.main.getValue(name)
|
install(HttpTimeout) {
|
||||||
try {
|
requestTimeoutMillis = 15_000
|
||||||
Log.i("Downloading $name from $url")
|
connectTimeoutMillis = 10_000
|
||||||
val file =
|
socketTimeoutMillis = 15_000
|
||||||
File(fontsDir, "$name${FILE_EXTENSIONS.first { url.endsWith(it) }}").apply { asFile() }
|
|
||||||
if (file.exists()) return@async
|
|
||||||
|
|
||||||
val client = HttpClient(CIO) {
|
|
||||||
install(UserAgent) { agent = Constants.USER_AGENT }
|
|
||||||
}
|
|
||||||
|
|
||||||
val response: HttpResponse = client.get(url)
|
|
||||||
if (response.status == HttpStatusCode.OK) file.writeBytes(response.body())
|
|
||||||
|
|
||||||
return@async
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Log.e("Failed to download fonts ($name from $url)", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.awaitAll()
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fontDef.main.keys.map { name ->
|
||||||
|
async {
|
||||||
|
val url = fontDef.main.getValue(name)
|
||||||
|
try {
|
||||||
|
val extension = FILE_EXTENSIONS.firstOrNull { url.substringBefore('?').endsWith(it) }
|
||||||
|
?: FILE_EXTENSIONS[0]
|
||||||
|
val file = File(fontsDir, "$name$extension").apply { asFile() }
|
||||||
|
if (file.exists()) return@async
|
||||||
|
|
||||||
|
Log.i("Downloading $name from $url")
|
||||||
|
val response: HttpResponse = client.get(url)
|
||||||
|
if (response.status == HttpStatusCode.OK) {
|
||||||
|
file.writeBytes(response.body())
|
||||||
|
} else {
|
||||||
|
Log.e("Failed to download fonts ($name from $url): HTTP ${response.status}")
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e("Failed to download fonts ($name from $url)", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.awaitAll()
|
||||||
|
} finally {
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules.appearance
|
package GoonXposed.xposed.modules.appearance
|
||||||
|
|
||||||
import android.R.color
|
import android.R.color
|
||||||
import android.app.AndroidAppHelper
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import ShiggyXposed.xposed.Module
|
import de.robv.android.xposed.XposedHelpers
|
||||||
|
import GoonXposed.xposed.Module
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
|
@ -13,8 +13,13 @@ object SysColorsModule : Module() {
|
||||||
private lateinit var context: WeakReference<Context>
|
private lateinit var context: WeakReference<Context>
|
||||||
private fun isSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
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) {
|
override fun buildPayload(builder: JsonObjectBuilder) {
|
||||||
context = WeakReference(AndroidAppHelper.currentApplication())
|
context = WeakReference(runCatching {
|
||||||
|
XposedHelpers.callStaticMethod(
|
||||||
|
XposedHelpers.findClass("android.app.ActivityThread", null), "currentApplication"
|
||||||
|
) as Context
|
||||||
|
}.getOrNull())
|
||||||
val accents = arrayOf("accent1", "accent2", "accent3", "neutral1", "neutral2")
|
val accents = arrayOf("accent1", "accent2", "accent3", "neutral1", "neutral2")
|
||||||
val shades = arrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
|
val shades = arrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
|
||||||
|
|
||||||
|
|
@ -37,7 +42,8 @@ object SysColorsModule : Module() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertToColor(id: Int): String {
|
private fun convertToColor(id: Int): String {
|
||||||
val clr = if (isSupported()) ContextCompat.getColor(context.get()!!, id) else 0
|
val ctx = context.get()
|
||||||
|
val clr = if (isSupported() && ctx != null) ContextCompat.getColor(ctx, id) else 0
|
||||||
return String.format("#%06X", 0xFFFFFF and clr)
|
return String.format("#%06X", 0xFFFFFF and clr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
package ShiggyXposed.xposed.modules.appearance
|
package GoonXposed.xposed.modules.appearance
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.core.graphics.toColorInt
|
import androidx.core.graphics.toColorInt
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.MethodHookBuilder
|
||||||
import ShiggyXposed.xposed.Utils.Companion.JSON
|
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.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.JsonObjectBuilder
|
import kotlinx.serialization.json.JsonObjectBuilder
|
||||||
|
|
@ -42,7 +46,7 @@ object ThemesModule : Module() {
|
||||||
|
|
||||||
private const val THEME_FILE = "current-theme.json"
|
private const val THEME_FILE = "current-theme.json"
|
||||||
|
|
||||||
|
@Deprecated("This method is deprecated in the parent class")
|
||||||
@ExperimentalSerializationApi
|
@ExperimentalSerializationApi
|
||||||
override fun buildPayload(builder: JsonObjectBuilder) {
|
override fun buildPayload(builder: JsonObjectBuilder) {
|
||||||
builder.apply {
|
builder.apply {
|
||||||
|
|
@ -81,10 +85,20 @@ object ThemesModule : Module() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hookTheme() {
|
fun hookTheme() {
|
||||||
val themeManager = param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt")
|
try {
|
||||||
val darkTheme = param.classLoader.loadClass("com.discord.theme.DarkerTheme")
|
val themeManager = param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt")
|
||||||
val lightTheme = param.classLoader.loadClass("com.discord.theme.LightTheme")
|
val darkTheme = param.classLoader.loadClass("com.discord.theme.DarkerTheme")
|
||||||
|
val lightTheme = param.classLoader.loadClass("com.discord.theme.LightTheme")
|
||||||
|
|
||||||
|
hookThemeInternal(themeManager, darkTheme, lightTheme)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
GoonXposed.xposed.Utils.Log.e("Failed to hook theme: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hookThemeInternal(
|
||||||
|
themeManager: Class<*>, darkTheme: Class<*>, lightTheme: Class<*>
|
||||||
|
) {
|
||||||
val theme = this.theme ?: return
|
val theme = this.theme ?: return
|
||||||
|
|
||||||
// Apply rawColors
|
// Apply rawColors
|
||||||
|
|
@ -106,42 +120,53 @@ object ThemesModule : Module() {
|
||||||
|
|
||||||
// If there's any rawColors value, hook the color getter
|
// If there's any rawColors value, hook the color getter
|
||||||
if (!theme.data.rawColors.isNullOrEmpty()) {
|
if (!theme.data.rawColors.isNullOrEmpty()) {
|
||||||
val getColorCompat = themeManager.getDeclaredMethod(
|
try {
|
||||||
"getColorCompat",
|
val getColorCompat = themeManager.getDeclaredMethod(
|
||||||
Resources::class.java,
|
"getColorCompat",
|
||||||
Int::class.javaPrimitiveType,
|
Resources::class.java,
|
||||||
Resources.Theme::class.java,
|
Int::class.javaPrimitiveType,
|
||||||
)
|
Resources.Theme::class.java,
|
||||||
|
)
|
||||||
|
|
||||||
val getColorCompatLegacy = themeManager.getDeclaredMethod(
|
val getColorCompatLegacy = themeManager.getDeclaredMethod(
|
||||||
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
|
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
|
||||||
)
|
)
|
||||||
|
|
||||||
val patch = MethodHookBuilder().run {
|
val patch = MethodHookBuilder().run {
|
||||||
before {
|
before {
|
||||||
val arg1 = args[0]
|
try {
|
||||||
val resources = if (arg1 is Context) arg1.resources else (arg1 as Resources)
|
val arg1 = args[0]
|
||||||
val name = resources.getResourceEntryName(args[1] as Int)
|
val resources = if (arg1 is Context) arg1.resources else (arg1 as Resources)
|
||||||
|
val name = resources.getResourceEntryName(args[1] as Int)
|
||||||
|
|
||||||
if (rawColorMap[name] != null) result = rawColorMap[name]
|
if (rawColorMap[name] != null) result = rawColorMap[name]
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build()
|
||||||
}
|
}
|
||||||
|
|
||||||
build()
|
getColorCompat.hook(patch)
|
||||||
|
getColorCompatLegacy.hook(patch)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
GoonXposed.xposed.Utils.Log.e("Failed to hook getColorCompat: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
getColorCompat.hook(patch)
|
|
||||||
getColorCompatLegacy.hook(patch)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse HEX colour string to INT. Takes "#RRGGBBAA" or "#RRGGBB"
|
// Parse HEX colour string to INT. Takes "#RRGGBBAA" or "#RRGGBB"
|
||||||
private fun hexStringToColorInt(hexString: String): Int {
|
private fun hexStringToColorInt(hexString: String): Int {
|
||||||
return if (hexString.length == 9) {
|
return try {
|
||||||
// Rearrange RRGGBBAA -> AARRGGBB so parseColor() is happy
|
if (hexString.length == 9) {
|
||||||
val alpha = hexString.substring(7, 9)
|
// Rearrange RRGGBBAA -> AARRGGBB so parseColor() is happy
|
||||||
val rrggbb = hexString.substring(1, 7)
|
val alpha = hexString.substring(7, 9)
|
||||||
"#$alpha$rrggbb".toColorInt()
|
val rrggbb = hexString.substring(1, 7)
|
||||||
} else hexString.toColorInt()
|
"#$alpha$rrggbb".toColorInt()
|
||||||
|
} else hexString.toColorInt()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) {
|
private fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package ShiggyXposed.xposed.modules.bridge
|
package GoonXposed.xposed.modules.bridge
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
|
@ -7,9 +7,9 @@ import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils
|
import GoonXposed.xposed.Utils
|
||||||
import ShiggyXposed.xposed.Utils.Companion.reloadApp
|
import GoonXposed.xposed.getAppInfo
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object AdditionalBridgeMethodsModule : Module() {
|
object AdditionalBridgeMethodsModule : Module() {
|
||||||
|
|
@ -48,11 +48,6 @@ object AdditionalBridgeMethodsModule : Module() {
|
||||||
|
|
||||||
file.writeText(contents as String)
|
file.writeText(contents as String)
|
||||||
}
|
}
|
||||||
|
|
||||||
BridgeModule.registerMethod("Shiggy.app.reload") {
|
|
||||||
reloadApp()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivity(activity: Activity) = with(activity) {
|
override fun onActivity(activity: Activity) = with(activity) {
|
||||||
|
|
@ -65,10 +60,10 @@ object AdditionalBridgeMethodsModule : Module() {
|
||||||
val clip = ClipData.newPlainText("Stack Trace", errorString)
|
val clip = ClipData.newPlainText("Stack Trace", errorString)
|
||||||
|
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle("ShiggyCord Error")
|
.setTitle("GoonCord Error")
|
||||||
.setMessage(
|
.setMessage(
|
||||||
"""
|
"""
|
||||||
ShiggyCord: $version
|
GoonCord: $version
|
||||||
${app.name}: ${app.version} (${app.versionCode})
|
${app.name}: ${app.version} (${app.versionCode})
|
||||||
Device: ${Build.MANUFACTURER} ${Build.MODEL}
|
Device: ${Build.MANUFACTURER} ${Build.MODEL}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package ShiggyXposed.xposed.modules.bridge
|
package GoonXposed.xposed.modules.bridge
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.BuildConfig
|
import GoonXposed.xposed.BuildConfig
|
||||||
import ShiggyXposed.xposed.Constants
|
import GoonXposed.xposed.Constants
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
import GoonXposed.xposed.hookMethod
|
||||||
|
import GoonXposed.xposed.method
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules.no_track
|
package GoonXposed.xposed.modules.no_track
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
import GoonXposed.xposed.hookMethod
|
||||||
|
import GoonXposed.xposed.safeLoadClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks Discord's crash reporting to disable Sentry initialization.
|
* Hooks Discord's crash reporting to disable Sentry initialization.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package ShiggyXposed.xposed.modules.no_track
|
package GoonXposed.xposed.modules.no_track
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import ShiggyXposed.xposed.Module
|
import GoonXposed.xposed.Module
|
||||||
import ShiggyXposed.xposed.Utils.Log
|
import GoonXposed.xposed.Utils.Log
|
||||||
|
import GoonXposed.xposed.hookMethod
|
||||||
|
import GoonXposed.xposed.safeLoadClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks Discord's deep links tracking to disable AppsFlyer initialization.
|
* Hooks Discord's deep links tracking to disable AppsFlyer initialization.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">ShiggyXposed</string>
|
<string name="app_name">GoonXposed</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue