diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1fc257c..2c82391 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,29 +25,29 @@ jobs: - name: "Setup directories" run: | - mkdir -p /tmp/pyonbuild/apks - mkdir -p /tmp/pyonbuild/tools + mkdir -p /tmp/build/apks + mkdir -p /tmp/build/tools - name: "Move debug APK" - run: mv ./app/build/outputs/apk/debug/app-debug.apk /tmp/pyonbuild/apks + run: mv ./app/build/outputs/apk/debug/app-debug.apk /tmp/build/apks - name: "Download uber-apk-signer" - run: wget -nv "https://github.com/patrickfav/uber-apk-signer/releases/download/v1.2.1/uber-apk-signer-1.2.1.jar" -O /tmp/pyonbuild/tools/uber-apk-signer.jar + run: wget -nv "https://github.com/patrickfav/uber-apk-signer/releases/download/v1.2.1/uber-apk-signer-1.2.1.jar" -O /tmp/build/tools/uber-apk-signer.jar - name: "Sign release APK" - run: java -jar /tmp/pyonbuild/tools/uber-apk-signer.jar --apks ./app/build/outputs/apk/release/app-release-unsigned.apk --out /tmp/pyonbuild/apks/ + run: java -jar /tmp/build/tools/uber-apk-signer.jar --apks ./app/build/outputs/apk/release/app-release-unsigned.apk --out /tmp/build/apks/ - name: "Rename release APK" - run: mv /tmp/pyonbuild/apks/app-release-aligned-debugSigned.apk /tmp/pyonbuild/apks/app-release.apk + run: mv /tmp/build/apks/app-release-aligned-debugSigned.apk /tmp/build/apks/app-release.apk - name: "Upload debug APK" uses: actions/upload-artifact@v3 with: name: app-debug - path: /tmp/pyonbuild/apks/app-debug.apk + path: /tmp/build/apks/app-debug.apk - name: "Upload release APK" uses: actions/upload-artifact@v3 with: name: app-release - path: /tmp/pyonbuild/apks/app-release.apk \ No newline at end of file + path: /tmp/build/apks/app-release.apk \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e58d3e4..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..19ce6a7 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index cbbe561..0897082 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,16 +4,15 @@ diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 9924903..0ad17cb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4fabd7c..c941490 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,15 +5,15 @@ plugins { } android { - namespace = "io.github.pyoncord.xposed" + namespace = "io.github.revenge.xposed" compileSdk = 33 defaultConfig { - applicationId = "io.github.pyoncord.xposed" + applicationId = "io.github.revenge.xposed" minSdk = 24 targetSdk = 33 - versionCode = 203 - versionName = "0.2.3" + versionCode = 204 + versionName = "0.2.4" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e52cff..84c00fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ android:value="true" /> + android:value="An Xposed module to inject Revenge, a mod for Discord's mobile apps." /> diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init index 9afddb0..9ad2900 100644 --- a/app/src/main/assets/xposed_init +++ b/app/src/main/assets/xposed_init @@ -1 +1 @@ -io.github.pyoncord.xposed.Main \ No newline at end of file +io.github.revenge.xposed.Main \ No newline at end of file diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/FontsModule.kt similarity index 93% rename from app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt rename to app/src/main/kotlin/io/github/revenge/xposed/FontsModule.kt index 85e574d..0481a6f 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/FontsModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/FontsModule.kt @@ -1,19 +1,16 @@ // credits to janisslsm from his PR: https://github.com/vendetta-mod/VendettaXposed/pull/17 // hooks are modified function from RN codebase -package io.github.pyoncord.xposed +package io.github.revenge.xposed import android.content.res.AssetManager import android.os.Build -import android.graphics.Color import android.graphics.Typeface import android.graphics.Typeface.CustomFallbackBuilder import android.graphics.fonts.Font import android.graphics.fonts.FontFamily import android.util.Log -import android.webkit.URLUtil import de.robv.android.xposed.XC_MethodReplacement -import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage import kotlinx.serialization.Serializable @@ -21,8 +18,6 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.* import java.io.IOException import java.io.File -import java.net.HttpURLConnection -import java.net.URL import kotlinx.coroutines.* import io.ktor.client.* @@ -41,7 +36,7 @@ data class FontDefinition( val main: Map, ) -class FontsModule: PyonModule() { +class FontsModule: Module() { private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") private val FONTS_ASSET_PATH = "fonts/" @@ -67,7 +62,7 @@ class FontsModule: PyonModule() { val assetManager: AssetManager = param.args[2] as AssetManager return createAssetTypeface(fontFamilyName, style, assetManager) } - }); + }) val fontDefFile = File(appInfo.dataDir, "files/pyoncord/fonts.json") if (!fontDefFile.exists()) return@with @@ -85,7 +80,7 @@ class FontsModule: PyonModule() { if (!fileName.startsWith(".")) { val fontName = fileName.split('.')[0] if (fontDef.main.keys.none { it == fontName }) { - Log.i("Bunny", "Deleting font file: $fileName") + Log.i("Revenge", "Deleting font file: $fileName") file.delete() } } @@ -97,12 +92,12 @@ class FontsModule: PyonModule() { async { val url = fontDef.main.getValue(name) try { - Log.i("Bunny", "Downloading $name from $url") + Log.i("Revenge", "Downloading $name from $url") val file = File(fontsDir, "$name${FILE_EXTENSIONS.first { url.endsWith(it) }}") if (file.exists()) return@async val client = HttpClient(CIO) { - install(UserAgent) { agent = "BunnyXposed" } + install(UserAgent) { agent = "RevengeXposed" } } val response: HttpResponse = client.get(url) @@ -113,7 +108,7 @@ class FontsModule: PyonModule() { return@async } catch (e: Throwable) { - Log.e("Bunny", "Failed to download fonts ($name from $url)", e) + Log.e("Revenge", "Failed to download fonts ($name from $url)", e) } } }.awaitAll() @@ -144,7 +139,7 @@ class FontsModule: PyonModule() { // ignore } - for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filter { it != null }) { + for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) { for (fileExtension in FILE_EXTENSIONS) { val fileName = java.lang.StringBuilder() .append(fontRootPath) @@ -220,7 +215,7 @@ class FontsModule: PyonModule() { // Lastly, after all those checks above, this is the original RN logic for // getting the typeface. - for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filter { it != null }) { + for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) { for (fileExtension in FILE_EXTENSIONS) { val fileName = java.lang.StringBuilder() .append(fontRootPath) diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt similarity index 73% rename from app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt rename to app/src/main/kotlin/io/github/revenge/xposed/Main.kt index c7718a4..46360af 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/Main.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt @@ -1,8 +1,6 @@ -package io.github.pyoncord.xposed +package io.github.revenge.xposed -import android.app.Activity -import android.app.AndroidAppHelper -import android.content.Context +import android.app.Activity import android.content.res.AssetManager import android.content.res.Resources import android.util.Log @@ -12,7 +10,7 @@ import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.callbacks.XC_LoadPackage -import io.github.pyoncord.xposed.BuildConfig +import io.github.revenge.xposed.BuildConfig import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* @@ -36,7 +34,7 @@ data class LoaderConfig( ) class Main : IXposedHookLoadPackage { - private val pyonModules: Array = arrayOf( + private val modules: Array = arrayOf( ThemeModule(), SysColorsModule(), FontsModule(), @@ -47,7 +45,7 @@ class Main : IXposedHookLoadPackage { put("loaderName", "BunnyXposed") put("loaderVersion", BuildConfig.VERSION_NAME) - for (module in pyonModules) { + for (module in modules) { module.buildJson(this) } } @@ -56,24 +54,34 @@ class Main : IXposedHookLoadPackage { } override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with (param) { - // val reactActivity = runCatching { - // lpparam.classLoader.loadClass("com.discord.react_activities.ReactActivity") - // }.getOrElse { return } // Package is not our the target app, return + val reactActivity = runCatching { + classLoader.loadClass("com.discord.react_activities.ReactActivity") + }.getOrElse { return@with } // Package is not our the target app, return - // XposedBridge.hookMethod(reactActivity.getDeclaredMethod("onCreate", Bundle::class.java), object : XC_MethodHook() { - // override fun beforeHookedMethod(param: MethodHookParam) { - // init(lpparam, param.thisObject as Activity) - // } - // }) - // } + var activity: Activity? = null; + val onActivityCreateCallback = mutableSetOf<(activity: Activity) -> Unit>() - // fun init(param: XC_LoadPackage.LoadPackageParam, activity: Activity) = with (param) { - // val catalystInstanceImpl = classLoader.loadClass("com.facebook.react.bridge.CatalystInstanceImpl") - val catalystInstanceImpl = runCatching { - classLoader.loadClass("com.facebook.react.bridge.CatalystInstanceImpl") - }.getOrElse { return@with } + XposedBridge.hookMethod(reactActivity.getDeclaredMethod("onCreate", Bundle::class.java), object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + activity = param.thisObject as Activity; + onActivityCreateCallback.forEach { cb -> cb(activity!!) } + onActivityCreateCallback.clear() + } + }) + + init(param) { cb -> + if (activity != null) cb(activity!!) + else onActivityCreateCallback.add(cb) + } + } + + private fun init( + param: XC_LoadPackage.LoadPackageParam, + onActivityCreate: ((activity: Activity) -> Unit) -> Unit + ) = with (param) { + val catalystInstanceImpl = classLoader.loadClass("com.facebook.react.bridge.CatalystInstanceImpl") - for (module in pyonModules) module.onInit(param) + for (module in modules) module.onInit(param) val loadScriptFromAssets = catalystInstanceImpl.getDeclaredMethod( "loadScriptFromAssets", @@ -124,14 +132,14 @@ class Main : IXposedHookLoadPackage { install(HttpTimeout) { requestTimeoutMillis = if (bundle.exists()) 3000 else 10000 } - install(UserAgent) { agent = "BunnyXposed" } + install(UserAgent) { agent = "RevengeXposed" } } val url = if (config.customLoadUrl.enabled) config.customLoadUrl.url - else "https://github.com/revenge-mod/Revenge/releases/latest/download/revenge.js" + else "https://github.com/revenge-mod/revenge-bundle/releases/latest/download/revenge.min.js" - Log.e("Bunny", "Fetching JS bundle from $url") + Log.e("Revenge", "Fetching JS bundle from $url") val response: HttpResponse = client.get(url) { headers { @@ -153,13 +161,19 @@ class Main : IXposedHookLoadPackage { return@async } catch (e: RedirectResponseException) { if (e.response.status != HttpStatusCode.NotModified) throw e; - Log.e("Bunny", "Server reponded with status code 304 - no changes to file") + Log.e("Revenge", "Server responded with status code 304 - no changes to file") } catch (e: Throwable) { - // activity.runOnUiThread { - // Toast.makeText(activity.applicationContext, "Failed to fetch JS bundle, Bunny may not load!", Toast.LENGTH_SHORT).show() - // } + onActivityCreate { activity -> + activity.runOnUiThread { + Toast.makeText( + activity.applicationContext, + "Failed to fetch JS bundle, Revenge may not load!", + Toast.LENGTH_SHORT + ).show() + } + } - Log.e("Bunny", "Failed to download bundle", e) + Log.e("Revenge", "Failed to download bundle", e) } } @@ -211,4 +225,4 @@ class Main : IXposedHookLoadPackage { }) } } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/PyonModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/Module.kt similarity index 79% rename from app/src/main/kotlin/io/github/pyoncord/xposed/PyonModule.kt rename to app/src/main/kotlin/io/github/revenge/xposed/Module.kt index ebcab56..b0416fd 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/PyonModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/Module.kt @@ -1,9 +1,9 @@ -package io.github.pyoncord.xposed +package io.github.revenge.xposed import de.robv.android.xposed.callbacks.XC_LoadPackage import kotlinx.serialization.json.JsonObjectBuilder -abstract class PyonModule { +abstract class Module { open fun buildJson(builder: JsonObjectBuilder) {} open fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) {} } \ No newline at end of file diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/SysColorsModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/SysColorsModule.kt similarity index 88% rename from app/src/main/kotlin/io/github/pyoncord/xposed/SysColorsModule.kt rename to app/src/main/kotlin/io/github/revenge/xposed/SysColorsModule.kt index 140b631..9ea6db3 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/SysColorsModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/SysColorsModule.kt @@ -1,4 +1,4 @@ -package io.github.pyoncord.xposed +package io.github.revenge.xposed import android.app.AndroidAppHelper import android.content.Context @@ -16,9 +16,9 @@ data class SysColors( val accent3: List ) -class SysColorsModule : PyonModule() { +class SysColorsModule : Module() { private lateinit var context: Context - fun isSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + private fun isSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S override fun buildJson(builder: JsonObjectBuilder) { context = AndroidAppHelper.currentApplication() @@ -43,7 +43,7 @@ class SysColorsModule : PyonModule() { } } - fun convertToColor(id: Int): String { + private fun convertToColor(id: Int): String { val clr = if (isSupported()) ContextCompat.getColor(context, id) else 0 return String.format("#%06X", 0xFFFFFF and clr) } diff --git a/app/src/main/kotlin/io/github/pyoncord/xposed/ThemeModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/ThemeModule.kt similarity index 91% rename from app/src/main/kotlin/io/github/pyoncord/xposed/ThemeModule.kt rename to app/src/main/kotlin/io/github/revenge/xposed/ThemeModule.kt index 7b1cb6c..175993d 100644 --- a/app/src/main/kotlin/io/github/pyoncord/xposed/ThemeModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/ThemeModule.kt @@ -1,4 +1,4 @@ -package io.github.pyoncord.xposed +package io.github.revenge.xposed import android.content.Context import android.graphics.Color @@ -35,7 +35,7 @@ data class Theme( val data: ThemeData ) -class ThemeModule : PyonModule() { +class ThemeModule : Module() { private lateinit var param: XC_LoadPackage.LoadPackageParam private var theme: Theme? = null @@ -58,14 +58,14 @@ class ThemeModule : PyonModule() { hookTheme() } - fun File.isValidish(): Boolean { + private fun File.isValidish(): Boolean { if (!this.exists()) return false val text = this.readText() - return !text.isBlank() && text != "{}" && text != "null" + return text.isNotBlank() && text != "{}" && text != "null" } - fun getTheme(): Theme? { + private fun getTheme(): Theme? { val filesDir = File(param.appInfo.dataDir, "files").apply { mkdirs() } val pyonDir = File(filesDir, "pyoncord").apply { mkdirs() } val themeFile = File(pyonDir, "current-theme.json") @@ -139,12 +139,12 @@ class ThemeModule : PyonModule() { } // Convert 0xRRGGBBAA to 0XAARRGGBB - fun hexStringToColorInt(hexString: String): Int { + private fun hexStringToColorInt(hexString: String): Int { val parsed = Color.parseColor(hexString) - return parsed.takeIf { hexString.length == 7 } ?: parsed and 0xFFFFFF or (parsed ushr 24) + return parsed.takeIf { hexString.length == 7 } ?: (parsed and 0xFFFFFF or (parsed ushr 24)) } - fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) { + private fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) { try { themeClass.getDeclaredMethod(methodName).let { method -> // Log.i("Hooking $methodName -> ${themeValue.toString(16)}") diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a327e5c..401896b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - BunnyXposed + RevengeXposed \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 808d13f..754c616 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ -rootProject.name = "BunnyXposed" +rootProject.name = "RevengeXposed" include(":app")