Merge remote-tracking branch 'upstream/master'

This commit is contained in:
OTKUSteyler 2025-11-22 22:04:25 +01:00
commit f47d6d1d93
25 changed files with 1800 additions and 330 deletions

View file

@ -1,65 +1,79 @@
name: Build APK name: Build APK
on: [push]
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
env:
JAVA_VERSION: 17
jobs: jobs:
build: build:
name: Build and push
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - name: Checkout
with: uses: actions/checkout@v4
fetch-depth: 0
- uses: actions/setup-java@v3 - name: Setup Java
uses: actions/setup-java@v4
with: with:
distribution: "temurin" distribution: temurin
java-version: "17" java-version: ${{ env.JAVA_VERSION }}
cache: gradle
- name: "Grant execution permission" - name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Make Gradle executable
run: chmod +x ./gradlew run: chmod +x ./gradlew
- name: "Build APKs" - name: Optional Keystore (if provided)
env:
KEYSTORE: ${{ secrets.KEYSTORE }}
run: | run: |
# Retry the Gradle build up to 3 times to mitigate transient cache/service errors if [ -n "$KEYSTORE" ]; then
ATTEMPT=0 echo "$KEYSTORE" | base64 -d > signing-key.jks
until [ $ATTEMPT -ge 3 ] ls -l signing-key.jks
do else
./gradlew build --no-daemon --stacktrace && break echo "No keystore provided, skipping..."
ATTEMPT=$((ATTEMPT+1))
echo "Gradle build failed, retrying ($ATTEMPT/3)..."
sleep 5
done
if [ $ATTEMPT -ge 3 ]; then
echo "Gradle build failed after 3 attempts"
exit 1
fi fi
- name: "Setup directories" - name: Assemble Debug and Release
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
GRADLE_OPTS: -Dorg.gradle.daemon=false
run: | run: |
mkdir -p /tmp/ShiggyXposedbuild/apks if [ -f signing-key.jks ]; then
mkdir -p /tmp/ShiggyXposedbuild/tools ./gradlew assembleDebug assembleRelease \
-Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/signing-key.jks \
-Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \
-Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
-Pandroid.injected.signing.key.password="$KEY_PASSWORD"
else
./gradlew assembleDebug assembleRelease
fi
- name: "Move debug APK" - name: Collect APKs
run: mv ./app/build/outputs/apk/debug/app-debug.apk /tmp/ShiggyXposedbuild/apks run: |
mkdir -p dist
cp app/build/outputs/apk/debug/*.apk dist/ || true
# Prefer signed release if produced
if ls app/build/outputs/apk/release/*-signed.apk >/dev/null 2>&1; then
cp app/build/outputs/apk/release/*-signed.apk dist/
else
cp app/build/outputs/apk/release/*.apk dist/
fi
ls -1 dist
- name: "Download uber-apk-signer" - name: Upload All APKs
run: wget -nv "https://github.com/patrickfav/uber-apk-signer/releases/download/v1.2.1/uber-apk-signer-1.2.1.jar" -O /tmp/ShiggyXposedbuild/tools/uber-apk-signer.jar
- name: "Sign release APK"
run: java -jar /tmp/ShiggyXposedbuild/tools/uber-apk-signer.jar --apks ./app/build/outputs/apk/release/app-release-unsigned.apk --out /tmp/ShiggyXposedbuild/apks/
- name: "Rename release APK"
run: mv /tmp/ShiggyXposedbuild/apks/app-release-aligned-debugSigned.apk /tmp/ShiggyXposedbuild/apks/app-release.apk
- name: "Upload debug APK"
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: app-debug name: apks
path: /tmp/ShiggyXposedbuild/apks/app-debug.apk path: dist/*.apk
if-no-files-found: error
- name: "Upload release APK"
uses: actions/upload-artifact@v4
with:
name: app-release
path: /tmp/ShiggyXposedbuild/apks/app-release.apk

View file

@ -1,19 +1,23 @@
name: Build Release name: Build Release
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
versionName: versionName:
required: true required: true
description: This releases version name description: This release's version name
default: "1.0.0" default: "1.0.0"
versionCode: versionCode:
required: true required: true
description: This releases version code description: This release's version code
default: "1000" default: "1000"
permissions: permissions:
contents: write contents: write
env:
JAVA_VERSION: 17
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -23,106 +27,79 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Setup JDK 17 - name: Setup JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
distribution: "temurin" java-version: ${{ env.JAVA_VERSION }}
java-version: 17 distribution: temurin
cache: gradle
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@v2.0.10 uses: android-actions/setup-android@v2
- name: Set Version - name: Set Version
uses: chkfung/android-version-actions@v1.1 uses: chkfung/android-version-actions@v1.1
with: with:
gradlePath: app/build.gradle.kts gradlePath: app/build.gradle.kts
versionCode: ${{github.event.inputs.versionCode}} versionCode: ${{ github.event.inputs.versionCode }}
versionName: ${{github.event.inputs.versionName}} versionName: ${{ github.event.inputs.versionName }}
- name: Bump Version - name: Commit Version Bump (if changed)
continue-on-error: true
run: | run: |
git config --local user.email "actions@github.com" git config user.email "actions@github.com"
git config --local user.name "GitHub Actions" git config user.name "GitHub Actions"
git add app/build.gradle.kts git add app/build.gradle.kts
if ! git diff --quiet app/build.gradle.kts; then if git diff --cached --quiet; then
git commit -m "chore: Bump version to ${{github.event.inputs.versionName}} (${{github.event.inputs.versionCode}})" echo "No version change."
git push
else else
echo "No changes to version file, skipping commit" git commit -m "chore: bump version to ${{ github.event.inputs.versionName }} (${{ github.event.inputs.versionCode }})"
git push
fi fi
- name: Build Signed APK - name: Prepare Keystore
run: | run: |
# Ensure keystore secret is present echo "${{ secrets.KEYSTORE }}" | base64 -d > signing-key.jks
if [ -z "${{ secrets.keystore }}" ]; then ls -l signing-key.jks
echo "Keystore secret missing, aborting"
exit 1
fi
echo "${{ secrets.keystore }}" | base64 -d > $GITHUB_WORKSPACE/signing-key.jks - name: Build Universal APKs
chmod +x ./gradlew env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
# Retry gradle up to 3 times to mitigate transient network/cache service issues KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
ATTEMPT=0 KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
until [ $ATTEMPT -ge 3 ] GRADLE_OPTS: -Dorg.gradle.daemon=false
do
./gradlew packageReleaseUniversalApk \
-Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/signing-key.jks \
-Pandroid.injected.signing.store.password=${{ secrets.keystore_password }} \
-Pandroid.injected.signing.key.alias=${{ secrets.key_alias }} \
-Pandroid.injected.signing.key.password=${{ secrets.key_password }} --no-daemon --stacktrace && break
ATTEMPT=$((ATTEMPT+1))
echo "Gradle build failed, retrying ($ATTEMPT/3)..."
sleep 5
done
if [ $ATTEMPT -ge 3 ]; then
echo "Gradle build failed after 3 attempts"
exit 1
fi
- name: Release
run: | run: |
set -e ./gradlew packageDebugUniversalApk packageReleaseUniversalApk \
echo "Locating built APK(s) under app/build/outputs..." -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/signing-key.jks \
# Find APK files produced by the build in common output locations -Pandroid.injected.signing.store.password="$KEYSTORE_PASSWORD" \
apk_paths=$(find app/build/outputs -type f -name "*.apk" 2>/dev/null || true) -Pandroid.injected.signing.key.alias="$KEY_ALIAS" \
if [ -z "$apk_paths" ]; then -Pandroid.injected.signing.key.password="$KEY_PASSWORD"
echo "ERROR: No APK files found under app/build/outputs"
echo "Diagnostics: top-level listing of app/build/outputs:" - name: Collect APKs
ls -la app/build/outputs || true run: |
echo "Diagnostics: recursive listing (files only):" mkdir dist
find app/build/outputs -type f -maxdepth 6 -print || true mv app/build/outputs/apk_from_bundle/debug/app-debug-universal.apk dist/app-debug.apk
echo "Failing the job so you can inspect the build logs and diagnostics above." mv app/build/outputs/apk_from_bundle/release/app-release-universal.apk dist/app-release.apk
exit 1 ls -l dist
fi
# Prepare a directory to collect APKs for the release - name: Upload APK Artifacts
mkdir -p release-apks uses: actions/upload-artifact@v4
echo "Copying found APK(s) to release-apks/ for upload:" with:
for apk in $apk_paths; do name: release-apks
echo " - $apk" path: dist/*.apk
cp "$apk" "release-apks/$(basename "$apk")" if-no-files-found: error
done
# Keep backward compatibility: if exactly one APK found, copy it to the root as app-release.apk - name: Create Tag and Release
apk_count=$(echo "$apk_paths" | wc -w)
if [ "$apk_count" -eq 1 ]; then
single_apk=$(echo "$apk_paths")
cp "$single_apk" ./app-release.apk
echo "Single APK copied to ./app-release.apk"
else
echo "Multiple APKs found ($apk_count). All copied to release-apks/"
fi
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
tag="${{ github.event.inputs.versionCode }}"
git tag "$tag"
git push origin "$tag"
# Create the GitHub release including all APK(s) we collected (debug + release)
gh release create "$tag" \
--title "${{ github.event.inputs.versionName }}" \
--generate-notes \
release-apks/*
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag=${{ github.event.inputs.versionCode }}
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
echo "Tag $tag already exists, skipping creation."
else
git tag "$tag"
git push origin "$tag"
fi
gh release view "$tag" >/dev/null 2>&1 || gh release create "$tag" \
--title "${{ github.event.inputs.versionName }}" \
--generate-notes \
dist/app-debug.apk dist/app-release.apk

3
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View file

@ -5,15 +5,15 @@ plugins {
} }
android { android {
namespace = "GoonXposed.xposed" namespace = "ShiggyXposed.xposed"
compileSdk = 36 compileSdk = 36
defaultConfig { defaultConfig {
applicationId = "GoonXposed.xposed" applicationId = "ShiggyXposed.xposed"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 1210 versionCode = 1220
versionName = "1.2.1" versionName = "1.2.2"
} }
buildTypes { buildTypes {
@ -28,33 +28,24 @@ android {
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
buildConfig = true
} }
kotlinOptions { jvmTarget = "17" }
buildFeatures { buildConfig = true }
}
repositories {
mavenCentral()
google()
maven("https://jitpack.io")
} }
dependencies { dependencies {
compileOnly("de.robv.android.xposed:api:82") compileOnly("de.robv.android.xposed:api:82")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") implementation("androidx.core:core-ktx:1.17.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("io.ktor:ktor-client-core:3.3.0") implementation("io.ktor:ktor-client-core:3.3.1")
implementation("io.ktor:ktor-client-cio:3.3.0") implementation("io.ktor:ktor-client-cio:3.3.1")
implementation("androidx.core:core-ktx:1.12.0") implementation("com.google.android.material:material:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation("com.google.android.material:material:1.12.0")
} }

View file

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<manifest <manifest
package="GoonXposed.xposed"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools"
>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:label="@string/app_name" android:label="@string/app_name"
@ -12,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 GoonCord into discord" android:value="Xposed module to inject ShiggyCord 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" />

View file

@ -1,4 +1,4 @@
package GoonXposed.xposed package ShiggyXposed.xposed
class Constants { class Constants {
companion object { companion object {
@ -9,10 +9,11 @@ class Constants {
const val CACHE_DIR = "cache/pyoncord" const val CACHE_DIR = "cache/pyoncord"
const val MAIN_SCRIPT_FILE = "bundle.js" const val MAIN_SCRIPT_FILE = "bundle.js"
const val LOG_TAG = "GoonXposed"
const val LOADER_NAME = "GoonXposed" const val LOG_TAG = "Shiggy"
const val USER_AGENT = "GoonXposed" const val LOADER_NAME = "ShiggyXposed"
const val USER_AGENT = "ShiggyXposed"
} }
} }

View file

@ -1,4 +1,4 @@
package GoonXposed.xposed package ShiggyXposed.xposed
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
@ -7,15 +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 GoonXposed.xposed.Utils.Log import ShiggyXposed.xposed.Utils.Log
import GoonXposed.xposed.modules.* import ShiggyXposed.xposed.modules.*
import GoonXposed.xposed.modules.appearance.FontsModule import ShiggyXposed.xposed.modules.appearance.FontsModule
import GoonXposed.xposed.modules.appearance.SysColorsModule import ShiggyXposed.xposed.modules.appearance.SysColorsModule
import GoonXposed.xposed.modules.appearance.ThemesModule import ShiggyXposed.xposed.modules.appearance.ThemesModule
import GoonXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule import ShiggyXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule
import GoonXposed.xposed.modules.bridge.BridgeModule import ShiggyXposed.xposed.modules.bridge.BridgeModule
import GoonXposed.xposed.modules.no_track.BlockCrashReportingModule import ShiggyXposed.xposed.modules.no_track.BlockCrashReportingModule
import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule import ShiggyXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
object HookStateHolder { object HookStateHolder {
@ -35,7 +35,7 @@ object HookStateHolder {
class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit { class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
private var hooked = false private var hooked = false
private val modules = mutableListOf<Module>( private val modules = mutableListOf(
HookScriptLoaderModule, HookScriptLoaderModule,
BridgeModule, BridgeModule,
AdditionalBridgeMethodsModule, AdditionalBridgeMethodsModule,

View file

@ -7,25 +7,28 @@ 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.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import kotlinx.serialization.json.JsonObjectBuilder
import java.io.File import java.io.File
import java.lang.reflect.Method import java.lang.reflect.Method
import kotlinx.serialization.json.JsonObjectBuilder
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: Long,
) )
abstract class Module { abstract class Module {
/** Builds a JSON payload to be injected into the JavaScript context. */ /**
@Deprecated( * Builds a JSON payload to be injected into the JavaScript context.
"This will be removed in future versions. Payloads can be replaced via synchronous bridge methods." */
) @Deprecated("This will be removed in future versions. Payloads can be replaced via synchronous bridge methods.")
open fun buildPayload(builder: JsonObjectBuilder) {} open fun buildPayload(builder: JsonObjectBuilder) {
}
/** Called during Zygote initialization. */ /**
* Called during Zygote initialization.
*/
open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {} open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {}
/** /**
@ -38,11 +41,10 @@ abstract class Module {
open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {} open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {}
/** /**
* Called after [Context] has been attached to a [android.content.ContextWrapper] via * Called after [Context] has been attached to a [android.content.ContextWrapper]
* [android.content.ContextWrapper.attachBaseContext]. * via [android.content.ContextWrapper.attachBaseContext].
* *
* This may be called multiple times for different [Context]s, e.g. when the app is killed by * This may be called multiple times for different [Context]s, e.g. when the app is killed by the system.
* the system.
* *
* For bridge methods that require access to [Context], register them here. * For bridge methods that require access to [Context], register them here.
*/ */
@ -55,25 +57,22 @@ abstract class Module {
* *
* Bridge methods must not be registered here, as it can be too late and cause crashes. * 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 * If you need to register bridge methods that require an [Activity], do it in [onContext] and make it async.
* make it async.
* *
* Once an [Activity] is available, execute the queued calls. Then from JS, call the method * Once an [Activity] is available, execute the queued calls. Then from JS, call the method asynchronously.
* asynchronously.
*/ */
open fun onActivity(activity: Activity) {} open fun onActivity(activity: Activity) {}
protected fun Context.getAppInfo(): AppInfo { protected fun Context.getAppInfo(): AppInfo {
val pInfo = packageManager.getPackageInfo(packageName, 0) val pInfo = packageManager.getPackageInfo(packageName, 0)
val versionCode = val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) pInfo.longVersionCode
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) pInfo.longVersionCode else @Suppress("DEPRECATION") pInfo.versionCode.toLong()
else @Suppress("DEPRECATION") pInfo.versionCode.toLong()
return AppInfo( return AppInfo(
packageManager.getApplicationLabel(applicationInfo).toString(), packageManager.getApplicationLabel(applicationInfo).toString(),
packageName, packageName,
pInfo.versionName ?: versionCode.toString(), pInfo.versionName ?: versionCode.toString(),
versionCode, versionCode,
) )
} }
@ -86,22 +85,21 @@ abstract class Module {
if (!this.isFile) this.deleteRecursively() if (!this.isFile) this.deleteRecursively()
} }
protected fun Class<*>.method(name: String, vararg params: Class<*>?): Method = protected fun Class<*>.method(
getDeclaredMethod(name, *params).apply { isAccessible = true } name: String, vararg params: Class<*>?
): Method = getDeclaredMethod(name, *params).apply {
isAccessible = true
}
protected fun ClassLoader.safeLoadClass(name: String): Class<*>? = protected fun ClassLoader.safeLoadClass(name: String): Class<*>? = runCatching { loadClass(name) }.getOrNull()
runCatching { loadClass(name) }.getOrNull()
protected fun Method.hook(hook: XC_MethodHook): XC_MethodHook.Unhook = protected fun Method.hook(hook: XC_MethodHook): XC_MethodHook.Unhook = XposedBridge.hookMethod(this, hook)
XposedBridge.hookMethod(this, hook)
protected fun Method.hook(block: MethodHookBuilder.() -> Unit): XC_MethodHook.Unhook = protected fun Method.hook(block: MethodHookBuilder.() -> Unit): XC_MethodHook.Unhook =
hook(MethodHookBuilder().apply(block).build()) hook(MethodHookBuilder().apply(block).build())
protected fun Class<*>.hookMethod( protected fun Class<*>.hookMethod(
name: String, name: String, vararg params: Class<*>?, block: MethodHookBuilder.() -> Unit
vararg params: Class<*>?,
block: MethodHookBuilder.() -> Unit
): XC_MethodHook.Unhook = method(name, *params).hook(MethodHookBuilder().apply(block).build()) ): XC_MethodHook.Unhook = method(name, *params).hook(MethodHookBuilder().apply(block).build())
protected class MethodHookBuilder { protected class MethodHookBuilder {
@ -116,36 +114,33 @@ abstract class Module {
afterBlock = block afterBlock = block
} }
fun build(): XC_MethodHook = fun build(): XC_MethodHook = object : XC_MethodHook() {
object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) {
override fun beforeHookedMethod(param: MethodHookParam) { val b = beforeBlock
val b = beforeBlock if (b != null) {
if (b != null) { val scope = HookScope(
val scope = param = param, proceed = { p ->
HookScope( super.beforeHookedMethod(p)
param = param, })
proceed = { p -> super.beforeHookedMethod(p) } scope.b()
) } else {
scope.b() super.beforeHookedMethod(param)
} 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)
}
}
} }
}
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)
}
}
}
} }
/** /**
@ -159,23 +154,19 @@ abstract class Module {
* @property param The [XC_MethodHook.MethodHookParam] for the current hook. * @property param The [XC_MethodHook.MethodHookParam] for the current hook.
* @property proceed Function that calls the super method. * @property proceed Function that calls the super method.
*/ */
protected class HookScope protected class HookScope internal constructor(
internal constructor( val param: XC_MethodHook.MethodHookParam, private val proceed: (XC_MethodHook.MethodHookParam) -> Unit
val param: XC_MethodHook.MethodHookParam,
private val proceed: (XC_MethodHook.MethodHookParam) -> Unit
) { ) {
/** /**
* Continues with the default XC_MethodHook super behavior. Equivalent to calling * Continues with the default XC_MethodHook super behavior.
* `super.beforeHookedMethod(param)` or `super.afterHookedMethod(param)` depending on the * Equivalent to calling `super.beforeHookedMethod(param)` or
* phase. * `super.afterHookedMethod(param)` depending on the phase.
*/ */
fun proceed() = proceed(param) fun proceed() = proceed(param)
val thisObject: Any? val thisObject: Any? get() = param.thisObject
get() = param.thisObject
val args: Array<Any?> val args: Array<Any?> get() = param.args
get() = param.args
var result: Any? var result: Any?
get() = param.result get() = param.result

View file

@ -0,0 +1,58 @@
package ShiggyXposed.xposed
import android.app.AlertDialog
import android.app.AndroidAppHelper
import android.content.Context
import android.content.Intent
import ShiggyXposed.xposed.modules.UpdaterModule
import kotlinx.serialization.json.Json
import java.io.File
import kotlin.system.exitProcess
class Utils {
companion object {
val JSON = Json { ignoreUnknownKeys = true }
fun reloadApp() {
val application = AndroidAppHelper.currentApplication()
val intent = application.packageManager.getLaunchIntentForPackage(application.packageName)
application.startActivity(Intent.makeRestartActivityTask(intent!!.component))
exitProcess(0)
}
fun showRecoveryAlert(context: Context) {
AlertDialog.Builder(context).setTitle("ShiggyCord Recovery Options")
.setItems(arrayOf("Reload", "Delete Script", "Reset Loader Config")) { _, which ->
when (which) {
0 -> {
reloadApp()
}
1 -> {
val bundleFile = File(
context.dataDir, "${Constants.CACHE_DIR}/${Constants.MAIN_SCRIPT_FILE}"
)
if (bundleFile.exists()) bundleFile.delete()
reloadApp()
}
2 -> {
UpdaterModule.resetLoaderConfig(context)
reloadApp()
}
}
}.show()
}
}
object Log {
fun e(msg: String) = android.util.Log.e(Constants.LOG_TAG, msg)
fun e(msg: String, throwable: Throwable) = android.util.Log.e(Constants.LOG_TAG, msg, throwable)
fun i(msg: String) = android.util.Log.i(Constants.LOG_TAG, msg)
fun i(msg: String, throwable: Throwable) = android.util.Log.i(Constants.LOG_TAG, msg, throwable)
fun w(msg: String) = android.util.Log.w(Constants.LOG_TAG, msg)
fun w(msg: String, throwable: Throwable) = android.util.Log.w(Constants.LOG_TAG, msg, throwable)
}
}

View file

@ -1,10 +1,10 @@
package GoonXposed.xposed.modules package ShiggyXposed.xposed.modules
import android.content.Context import android.content.Context
import android.util.AtomicFile import android.util.AtomicFile
import GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import ShiggyXposed.xposed.Utils.Log
import GoonXposed.xposed.modules.bridge.BridgeModule import ShiggyXposed.xposed.modules.bridge.BridgeModule
import java.io.* import java.io.*
/** /**

View file

@ -0,0 +1,23 @@
package ShiggyXposed.xposed.modules
import android.content.res.Resources
import de.robv.android.xposed.callbacks.XC_LoadPackage
import ShiggyXposed.xposed.Constants.Companion.TARGET_PACKAGE
import ShiggyXposed.xposed.Module
/**
* Hooks [Resources.getIdentifier] to fix resource package name mismatch.
*/
object FixResourcesModule : Module() {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
if (packageName != TARGET_PACKAGE) {
Resources::class.java.hookMethod(
"getIdentifier", String::class.java, String::class.java, String::class.java
) {
before {
if (args[2] == packageName) args[2] = TARGET_PACKAGE
}
}
}
}
}

View file

@ -0,0 +1,121 @@
package ShiggyXposed.xposed.modules
import android.content.res.AssetManager
import android.content.res.XModuleResources
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage
import ShiggyXposed.xposed.Constants
import ShiggyXposed.xposed.HookStateHolder
import ShiggyXposed.xposed.Module
import ShiggyXposed.xposed.Utils.Log
import ShiggyXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import java.io.File
import java.lang.reflect.Method
/**
* Hooks React Native's script loading methods to load custom scripts and bundles.
*
* Preload scripts should be placed in the [PRELOADS_DIR] directory inside the module's files directory.
*
* The main bundle should be placed in the [Constants.CACHE_DIR] directory named [Constants.MAIN_SCRIPT_FILE].
* If the bundle file does not exist, it will attempt to load `assets://Shiggy.bundle` from the module's assets.
*/
object HookScriptLoaderModule : Module() {
private lateinit var preloadsDir: File
private lateinit var mainScript: File
private lateinit var modulePath: String
private lateinit var resources: XModuleResources
const val PRELOADS_DIR = "preloads"
override fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {
this@HookScriptLoaderModule.modulePath = startupParam.modulePath
}
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
val cacheDir = File(appInfo.dataDir, Constants.CACHE_DIR).apply { asDir() }
val filesDir = File(appInfo.dataDir, Constants.FILES_DIR).apply { asDir() }
preloadsDir = File(filesDir, PRELOADS_DIR).apply { asDir() }
mainScript = File(cacheDir, Constants.MAIN_SCRIPT_FILE).apply { asFile() }
listOf(
"com.facebook.react.runtime.ReactInstance\$loadJSBundle$1",
"com.facebook.react.runtime.ReactInstance$1",
// TODO: Remove once Discord fully switches to Bridgeless
"com.facebook.react.bridge.CatalystInstanceImpl"
).mapNotNull { classLoader.safeLoadClass(it) }.forEach { hook(it) }
}
private fun hook(instance: Class<*>) = runCatching {
val loadScriptFromAssets = instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType
)
val loadScriptFromFile = instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
)
loadScriptFromAssets.hook {
before {
Log.i("Received call to loadScriptFromAssets: ${args[1]} (sync: ${args[2]})")
runCustomScripts(loadScriptFromFile, loadScriptFromAssets)
}
}
loadScriptFromFile.hook {
before {
Log.i("Received call to loadScriptFromFile: ${args[0]} (sync: ${args[2]})")
runCustomScripts(loadScriptFromFile, loadScriptFromAssets)
}
}
}.onFailure {
Log.e("Failed to hook script loading methods in ${instance.name}:", it)
}
private fun HookScope.runCustomScripts(loadScriptFromFile: Method, loadScriptFromAssets: Method) {
Log.i("Running custom scripts...")
runBlocking {
val ready = async { HookStateHolder.readyDeferred.join() }
val download = async { UpdaterModule.downloadScript().join() }
awaitAll(ready, download)
}
val loadSynchronously = args[2]
val runScriptFile = { file: File ->
Log.i("Loading script: ${file.absolutePath}")
XposedBridge.invokeOriginalMethod(
loadScriptFromFile, thisObject, arrayOf(file.absolutePath, file.absolutePath, loadSynchronously)
)
Unit
}
try {
preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile)
if (mainScript.exists()) runScriptFile(mainScript)
else {
Log.i("Main script does not exist, falling back")
if (!::resources.isInitialized) resources = XModuleResources.createInstance(modulePath, null)
XposedBridge.invokeOriginalMethod(
loadScriptFromAssets,
thisObject,
arrayOf(resources.assets, "assets://Shiggy.bundle", loadSynchronously)
)
}
} catch (e: Throwable) {
Log.e("Unable to run scripts:", e)
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,80 @@
package ShiggyXposed.xposed.modules
import android.content.res.AssetManager
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage
import ShiggyXposed.xposed.BuildConfig
import ShiggyXposed.xposed.Constants
import ShiggyXposed.xposed.Module
import ShiggyXposed.xposed.Utils.Companion.JSON
import ShiggyXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import java.io.File
// TODO: Remove this redundant module and merge its functionality into AdditionalBridgeMethodsModule if needed
class PayloadGlobalModule(private val modules: List<Module>) : Module() {
private companion object {
const val GLOBAL_NAME = "__PYON_LOADER__"
}
private fun getPayloadString(): String = JSON.encodeToString(
buildJsonObject {
put("loaderName", Constants.LOADER_NAME)
put("loaderVersion", BuildConfig.VERSION_NAME)
@Suppress("DEPRECATION") for (module in modules) module.buildPayload(this)
})
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
val catalystInstance = classLoader.safeLoadClass("com.facebook.react.bridge.CatalystInstanceImpl")
val scriptLoader = classLoader.safeLoadClass("com.facebook.react.runtime.ReactInstance\$loadJSBundle$1")
?: classLoader.safeLoadClass("com.facebook.react.runtime.ReactInstance$1")
val setGlobalVariable: (XC_MethodHook.MethodHookParam, String, String) -> Unit = { param, key, json ->
runCatching {
// Attempt old CatalystInstanceImpl method
catalystInstance!!.method(
"setGlobalVariable", String::class.java, String::class.java
).invoke(param.thisObject, key, json)
}.onFailure {
// Failed (likely because it's a stub), use a workaround
val preloadsDir = File("${appInfo.dataDir}/${Constants.FILES_DIR}", PRELOADS_DIR)
File(preloadsDir, "rv_globals_$key.js").apply {
writeText("this[${JSON.encodeToString(key)}]=$json")
XposedBridge.invokeOriginalMethod(
scriptLoader!!.method(
"loadScriptFromFile",
String::class.java,
String::class.java,
Boolean::class.javaPrimitiveType
), param.thisObject, arrayOf(absolutePath, absolutePath, param.args[2])
)
delete()
}
}
}
val hook = MethodHookBuilder().run {
before {
setGlobalVariable(param, GLOBAL_NAME, getPayloadString())
}
build()
}
listOf(catalystInstance, scriptLoader).forEach { if (it != null) hook(it, hook) }
}
private fun hook(instance: Class<*>, hook: XC_MethodHook) {
instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType,
).hook(hook)
instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
).hook(hook)
}
}

View file

@ -1,10 +1,10 @@
package GoonXposed.xposed.modules package ShiggyXposed.xposed.modules
import android.content.Context import android.content.Context
import android.util.AtomicFile import android.util.AtomicFile
import GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import ShiggyXposed.xposed.Utils.Log
import GoonXposed.xposed.modules.bridge.BridgeModule import ShiggyXposed.xposed.modules.bridge.BridgeModule
import java.io.* import java.io.*
/** /**

View file

@ -0,0 +1,155 @@
package ShiggyXposed.xposed.modules
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.util.AtomicFile
import android.widget.Toast
import androidx.core.util.writeBytes
import de.robv.android.xposed.callbacks.XC_LoadPackage
import ShiggyXposed.xposed.Constants
import ShiggyXposed.xposed.Module
import ShiggyXposed.xposed.Utils
import ShiggyXposed.xposed.Utils.Companion.JSON
import ShiggyXposed.xposed.Utils.Companion.reloadApp
import ShiggyXposed.xposed.Utils.Log
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
import java.io.File
import java.lang.ref.WeakReference
@Serializable
data class CustomLoadUrl(
val enabled: Boolean = false, val url: String = ""
)
@Serializable
data class LoaderConfig(
val customLoadUrl: CustomLoadUrl = CustomLoadUrl()
)
/**
* Module that updates the JS bundle by downloading it from a remote URL.
*
* Shows dialogs when failed allowing retry.
*/
object UpdaterModule : Module() {
private lateinit var config: LoaderConfig
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var lastActivity: WeakReference<Activity>? = null
private lateinit var cacheDir: File
private lateinit var bundle: File
private lateinit var etag: File
private const val TIMEOUT_CACHED = 5000L
private const val TIMEOUT = 10000L
private const val ETAG_FILE = "etag.txt"
private const val CONFIG_FILE = "loader.json"
private const val DEFAULT_BUNDLE_URL =
"https://github.com/kmmiio99o/ShiggyCord/releases/latest/download/shiggycord.js"
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
cacheDir = File(appInfo.dataDir, Constants.CACHE_DIR).apply { mkdirs() }
val filesDir = File(appInfo.dataDir, Constants.FILES_DIR).apply { mkdirs() }
bundle = File(cacheDir, Constants.MAIN_SCRIPT_FILE)
etag = File(cacheDir, ETAG_FILE)
val configFile = File(filesDir, CONFIG_FILE)
config = runCatching {
if (configFile.exists()) {
JSON.decodeFromString<LoaderConfig>(configFile.readText())
} else LoaderConfig()
}.getOrDefault(LoaderConfig())
}
fun downloadScript(activity: Activity? = null): Job = scope.launch {
try {
HttpClient(CIO) {
expectSuccess = false
install(UserAgent) { agent = Constants.USER_AGENT }
install(HttpRedirect) {}
}.use { client ->
val url = config.customLoadUrl.takeIf { it.enabled }?.url ?: DEFAULT_BUNDLE_URL
Log.i("Fetching JS bundle from: $url")
val response: HttpResponse = client.get(url) {
headers {
if (etag.exists() && bundle.exists()) {
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) {
HttpStatusCode.OK -> {
val bytes: ByteArray = response.body()
AtomicFile(bundle).writeBytes(bytes)
val newTag = response.headers[HttpHeaders.ETag]
if (!newTag.isNullOrEmpty()) etag.writeText(newTag) else etag.delete()
Log.i("Bundle updated (${bytes.size} bytes)")
// This is a retry, so we show a dialog
if (activity != null) {
withContext(Dispatchers.Main) {
AlertDialog.Builder(activity).setTitle("ShiggyCord Update Successful")
.setMessage("A reload is required for changes to take effect.")
.setPositiveButton("Reload") { dialog, _ ->
reloadApp()
dialog.dismiss()
}.setCancelable(false).show()
}
}
}
HttpStatusCode.NotModified -> {
Log.i("Server responded with 304, no changes")
}
else -> {
throw ResponseException(response, "Received status: ${response.status}")
}
}
}
} catch (e: Throwable) {
Log.e("Failed to download script", e)
showErrorDialog(e)
}
}
override fun onActivity(activity: Activity) {
lastActivity = WeakReference(activity)
}
fun showErrorDialog(e: Throwable) {
val activity = lastActivity?.get() ?: return
activity.runOnUiThread {
}
}
fun resetLoaderConfig(context: Context) {
val filesDir = File(context.dataDir, Constants.FILES_DIR)
val config = File(filesDir, CONFIG_FILE)
if (config.exists()) config.delete()
}
}

View file

@ -1,4 +1,4 @@
package GoonXposed.xposed.modules.appearance package ShiggyXposed.xposed.modules.appearance
import android.content.res.AssetManager import android.content.res.AssetManager
import android.graphics.Typeface import android.graphics.Typeface
@ -9,10 +9,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 GoonXposed.xposed.Constants import ShiggyXposed.xposed.Constants
import GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON import ShiggyXposed.xposed.Utils.Companion.JSON
import GoonXposed.xposed.Utils.Log import ShiggyXposed.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.*

View file

@ -1,11 +1,11 @@
package GoonXposed.xposed.modules.appearance package ShiggyXposed.xposed.modules.appearance
import android.R.color import android.R.color
import android.app.AndroidAppHelper 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 GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.lang.ref.WeakReference import java.lang.ref.WeakReference

View file

@ -1,12 +1,12 @@
package GoonXposed.xposed.modules.appearance package ShiggyXposed.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 GoonXposed.xposed.Constants import ShiggyXposed.xposed.Constants
import GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON import ShiggyXposed.xposed.Utils.Companion.JSON
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

View file

@ -1,4 +1,4 @@
package GoonXposed.xposed.modules.bridge package ShiggyXposed.xposed.modules.bridge
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
@ -7,8 +7,8 @@ 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 GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils import ShiggyXposed.xposed.Utils
import java.io.File import java.io.File
object AdditionalBridgeMethodsModule : Module() { object AdditionalBridgeMethodsModule : Module() {

View file

@ -1,10 +1,10 @@
package GoonXposed.xposed.modules.bridge package ShiggyXposed.xposed.modules.bridge
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.BuildConfig import ShiggyXposed.xposed.BuildConfig
import GoonXposed.xposed.Constants import ShiggyXposed.xposed.Constants
import GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import ShiggyXposed.xposed.Utils.Log
import java.lang.reflect.Method import java.lang.reflect.Method
/** /**

View file

@ -0,0 +1,50 @@
package ShiggyXposed.xposed.modules.no_track
import android.content.Context
import de.robv.android.xposed.callbacks.XC_LoadPackage
import ShiggyXposed.xposed.Module
import ShiggyXposed.xposed.Utils.Log
/**
* Hooks Discord's crash reporting to disable Sentry initialization.
* While Discord doesn't have Sentry auto-initialization on by default, we still hook the content provider just to be safe.
*/
object BlockCrashReportingModule : Module() {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
val crashReportingClass = classLoader.safeLoadClass("com.discord.crash_reporting.CrashReporting")
crashReportingClass?.apply {
// This only exists on 30720x and above
runCatching {
hookMethod("isDisabled") {
before {
Log.i("Forced CrashReporting.isDisabled() to true")
result = true
}
}
}.onFailure {
// In older versions, this hook works fine.
// Hooking this on 30720x will result in a crash after a few seconds,
// since Discord asserts initialization when setting a Sentry tag.
hookMethod(
"init", Context::class.java, String::class.java
) {
before {
Log.i("Blocked CrashReporting initialization")
result = null
}
}
}
}
val sentryInitProviderClass = classLoader.safeLoadClass("io.sentry.android.core.SentryInitProvider")
sentryInitProviderClass?.hookMethod("onCreate") {
before {
Log.i("Blocked SentryInitProvider initialization")
result = true
}
}
return@with
}
}

View file

@ -1,9 +1,9 @@
package GoonXposed.xposed.modules.no_track package ShiggyXposed.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 GoonXposed.xposed.Module import ShiggyXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import ShiggyXposed.xposed.Utils.Log
/** /**
* Hooks Discord's deep links tracking to disable AppsFlyer initialization. * Hooks Discord's deep links tracking to disable AppsFlyer initialization.

View file

@ -1,46 +0,0 @@
package GoonXposed.xposed.modules.no_track
import android.content.Context
import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log
/**
* Hooks Discord's crash reporting to disable Sentry initialization.
* While Discord doesn't have Sentry auto-initialization on by default, we still hook the content provider just to be safe.
*/
object BlockCrashReportingModule : Module() {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
val crashReportingClass = classLoader.safeLoadClass("com.discord.crash_reporting.CrashReporting")
crashReportingClass?.apply {
// Hooking this will result in a crash after a few seconds, since Discord expects initialization to complete when setting a Sentry tag.
// So we also hook isDisabled to make sure those calls are no-ops.
hookMethod(
"init", Context::class.java, String::class.java
) {
before {
Log.i("Blocked CrashReporting initialization")
result = null
}
}
runCatching {
hookMethod("isDisabled") {
before {
Log.i("Forced CrashReporting.isDisabled() to true")
result = true
}
}
}
val sentryInitProviderClass = classLoader.safeLoadClass("io.sentry.android.core.SentryInitProvider")
sentryInitProviderClass?.hookMethod("onCreate") {
before {
Log.i("Blocked SentryInitProvider initialization")
result = true
}
}
return@with
}
}
}

View file

@ -1,2 +1,2 @@
rootProject.name = "KettuXposed" rootProject.name = "ShiggyXposed"
include(":app") include(":app")