Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
82666b1
feat(Sunflower): Added library replacement step
secp192k1 Jun 7, 2026
06ea61a
feat(Sunflower): Custom core
secp192k1 Jun 10, 2026
6923010
le assets
secp192k1 Jun 10, 2026
6e46c03
fix(Sunflower): Screenshare screen rotation
secp192k1 Jun 11, 2026
33664f7
fix(Sunflower): Screenshare and camera rotation
secp192k1 Jun 12, 2026
2adeacf
fix(Sunflower): Voice native calls against disposed state
secp192k1 Jun 12, 2026
44af215
feat(ManifestPatcher): Foreground service to keep streaming alive
secp192k1 Jun 12, 2026
e2dd834
feat: Implement DAVE/MLS epoch sync and encoder queue stats
secp192k1 Jun 12, 2026
49dae98
chore: Improve logs and add Opcodes
secp192k1 Jun 13, 2026
3390200
fix: Clear video sink on view update
secp192k1 Jun 13, 2026
d362202
refactor: Centralize logging format for WebRTC classes
secp192k1 Jun 13, 2026
480e8a3
chore(Sunflower): Optimize imports across module
secp192k1 Jun 13, 2026
a018f74
sync: chore: bump core version to 2.9.5
secp192k1 Jun 13, 2026
606c1e8
Merge branch 'Aliucord:main' into feat/sunflower
secp192k1 Jun 14, 2026
264d93b
chore: Revert custom core
secp192k1 Jun 15, 2026
8171de3
chore: Revert custom patches
secp192k1 Jun 15, 2026
39f4a17
chore: Revert custom injector
secp192k1 Jun 15, 2026
7e1e27a
chore: Remove custom core string
secp192k1 Jun 15, 2026
f4ec2a7
feat(DownloadStep): Add WebRTC dex step
secp192k1 Jun 17, 2026
bb81066
chore: Remove native libs
secp192k1 Jun 17, 2026
b11f868
refactor: Migrate to Aliuvoice AAR
secp192k1 Jun 17, 2026
d4c42b8
fix(Patcher): Load all dex files from AAR
secp192k1 Jun 19, 2026
bc1cdf7
fix(Patcher): Bump dex priority
secp192k1 Jun 19, 2026
efb3e90
feat: Download native libs from maven splits
secp192k1 Jun 22, 2026
15ee360
Merge branch 'Aliucord:main' into feat/sunflower
secp192k1 Jun 23, 2026
ecda02d
feat(ManifestPatcher): Add foreground permissions
secp192k1 Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,18 @@ class PathManager(
fun cachedKotlinDex(version: SemVer) = patchingDownloadDir
.resolve("kotlin-stdlib")
.resolve("$version.dex")

/**
* Resolve a specific path for a versioned cached Aliuvoice build
*/
fun cachedAliuvoiceAAR(version: SemVer) = patchingDownloadDir
.resolve("aliuvoice")
.resolve("$version.aar")

/**
* Resolve a specific path for a cached Discord split APK carrying libdiscord.so.
*/
fun cachedLibdiscordApk(version: Int, abi: String) = patchingDownloadDir
.resolve("libdiscord/$version")
.resolve("config-$abi.apk")
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import io.ktor.client.request.url
import io.ktor.http.*

class AliucordMavenService(private val http: HttpService) {
suspend fun getAliuhookVersion(force: Boolean = false): ApiResponse<SemVer> {
suspend fun getMavenMeta(artifactName: String, force: Boolean): ApiResponse<SemVer> {
val metadataResponse = http.request<String> {
url("${BuildConfig.MAVEN_URL}/com/aliucord/Aliuhook/maven-metadata.xml")
url("${BuildConfig.MAVEN_URL}/com/aliucord/$artifactName/maven-metadata.xml")

if (!force) {
cacheControl(CacheControl.MaxAge(maxAgeSeconds = 60 * 30)) // 30 min
Expand All @@ -23,13 +23,25 @@ class AliucordMavenService(private val http: HttpService) {
val versionString = "<release>(.+?)</release>".toRegex()
.find(it)
?.groupValues?.get(1)
?: return ApiResponse.Error(ApiError(HttpStatusCode.OK, "No version in the aliuhook artifact metadata"))
?: return ApiResponse.Error(ApiError(HttpStatusCode.OK, "No version in the $artifactName artifact metadata"))

SemVer.parseOrNull(versionString)
?: return ApiResponse.Error(ApiError(HttpStatusCode.OK, "Invalid latest aliuhook version!"))
?: return ApiResponse.Error(ApiError(HttpStatusCode.OK, "Invalid latest $artifactName version!"))
}
}

suspend fun getAliuhookVersion(force: Boolean = false): ApiResponse<SemVer> =
getMavenMeta("Aliuhook", force)

suspend fun getAliuvoiceVersion(force: Boolean = false): ApiResponse<SemVer> =
getMavenMeta("Aliuvoice", force)

fun getAliuhookUrl(version: SemVer): String =
"${BuildConfig.MAVEN_URL}/com/aliucord/Aliuhook/$version/Aliuhook-$version.aar"

fun getAliuvoiceUrl(version: SemVer): String =
"${BuildConfig.MAVEN_URL}/com/aliucord/Aliuvoice/$version/Aliuvoice-$version.aar"

fun getLibraryApkUrl(version: Int, abi: String): String =
"${BuildConfig.MAVEN_URL}/com/discord/discord/$version/config-$abi.apk"
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class KotlinPatchRunner(
DownloadInjectorStep(options.customInjector),
DownloadAliuhookStep(),
DownloadKotlinStep(),
DownloadAliuvoiceStep(),
DownloadLibdiscordStep(),
DownloadPatchesStep(options.customPatches),
CopyDependenciesStep(),

Expand All @@ -34,6 +36,7 @@ class KotlinPatchRunner(
PatchCertsStep(),
ReorganizeDexStep(),
AddAliuhookLibsStep(),
ReplaceLibdiscordStep(),
SaveMetadataStep(options),

// Install
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.aliucord.manager.patcher.steps.download

import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.services.AliucordMavenService
import com.aliucord.manager.network.utils.SemVer
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.base.IDexProvider
import com.aliucord.manager.patcher.steps.patch.ReorganizeDexStep
import com.aliucord.manager.patcher.steps.patch.ReplaceLibdiscordStep
import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep
import com.github.diamondminer88.zip.ZipReader
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

/**
* Download a packaged AAR of the latest Aliuvoice build from the Aliucord maven.
* The AAR carries the prebuilt webrtc.dex (org.webrtc). libdiscord.so is sourced
* separately by [ReplaceLibdiscordStep] from the Discord split APK.
* Provides [ReorganizeDexStep] with the dex through the [IDexProvider] implementation.
*/
@Stable
class DownloadAliuvoiceStep : DownloadStep<SemVer>(), IDexProvider, KoinComponent {
private val paths: PathManager by inject()
private val maven: AliucordMavenService by inject()

override val localizedName = R.string.patch_step_dl_aliuvoice

override fun getRemoteUrl(container: StepRunner) =
maven.getAliuvoiceUrl(getVersion(container))

override fun getVersion(container: StepRunner) =
container.getStep<FetchInfoStep>().aliuvoiceVersion

override fun getStoredFile(container: StepRunner) =
paths.cachedAliuvoiceAAR(getVersion(container))

override val dexPriority = 1
override val dexCount get() = dexFiles?.size ?: 1

// R8/d8 may split across webrtc.dex + classes*.dex
private var dexFiles: List<ByteArray>? = null

override suspend fun execute(container: StepRunner) {
super.execute(container)

dexFiles = ZipReader(getStoredFile(container)).use { aar ->
aar.entryNames
.filter { !it.contains('/') && it.endsWith(".dex") }
.sorted()
.map { name ->
aar.openEntry(name)?.read() ?: throw IllegalStateException("Failed to read $name from aliuvoice aar")
}
}

if (dexFiles!!.isEmpty())
throw IllegalStateException("No prebuilt dex files in downloaded aliuvoice build")
}

override fun getDexFiles(container: StepRunner): List<ByteArray> =
dexFiles ?: throw IllegalStateException("Aliuvoice dex files not loaded, download step likely failed")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.aliucord.manager.patcher.steps.download

import android.os.Build
import androidx.compose.runtime.Stable
import com.aliucord.manager.R
import com.aliucord.manager.manager.PathManager
import com.aliucord.manager.network.services.AliucordMavenService
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.base.DownloadStep
import com.aliucord.manager.patcher.steps.patch.ReplaceLibdiscordStep
import com.aliucord.manager.patcher.steps.prepare.FetchInfoStep
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

/**
* Download the Discord split APK matching the device's primary ABI, which carries the
* libdiscord.so used as the voice engine (consumed by [ReplaceLibdiscordStep]).
*/
@Stable
class DownloadLibdiscordStep : DownloadStep<Int>(), KoinComponent {
private val paths: PathManager by inject()
private val maven: AliucordMavenService by inject()

val currentDeviceArch: String = Build.SUPPORTED_ABIS.first()

override val localizedName = R.string.patch_step_dl_libdiscord

override fun getVersion(container: StepRunner) =
container.getStep<FetchInfoStep>().libdiscordVersion

override fun getRemoteUrl(container: StepRunner) =
maven.getLibraryApkUrl(getVersion(container), currentDeviceArch)

override fun getStoredFile(container: StepRunner) =
paths.cachedLibdiscordApk(getVersion(container), currentDeviceArch)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.aliucord.manager.patcher.steps.patch

import android.os.Build
import com.aliucord.manager.R
import com.aliucord.manager.patcher.StepRunner
import com.aliucord.manager.patcher.steps.StepGroup
import com.aliucord.manager.patcher.steps.base.Step
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
import com.aliucord.manager.patcher.steps.download.DownloadLibdiscordStep
import com.github.diamondminer88.zip.*
import org.koin.core.component.KoinComponent


/**
* Replace Discord's bundled libdiscord.so with the one from 333.5 (333205),
* which Sunflower's Kotlin media-engine layer.
*
* Written uncompressed + unaligned here; [com.aliucord.manager.patcher.steps.install.AlignmentStep] re-aligns all .so to 16KiB.
*/
class ReplaceLibdiscordStep : Step(), KoinComponent {
override val group = StepGroup.Patch
override val localizedName = R.string.patch_step_replace_libdiscord

override suspend fun execute(container: StepRunner) {
val currentDeviceArch = Build.SUPPORTED_ABIS.first()
val apk = container.getStep<CopyDependenciesStep>().apk
val libApk = container.getStep<DownloadLibdiscordStep>().getStoredFile(container)

val libBytes = ZipReader(libApk).use { it.openEntry("lib/$currentDeviceArch/libdiscord.so")?.read() }

if (libBytes == null) {
container.log("No libdiscord.so for arch $currentDeviceArch in split apk; leaving original engine in place")
return
}

val apkLibPath = "lib/$currentDeviceArch/libdiscord.so"
val existing = ZipReader(apk).use { it.entryNames.toHashSet() }

ZipWriter(apk, true).use { zip ->
container.log("Writing $apkLibPath from libdiscord split (${libBytes.size} bytes); existing=${apkLibPath in existing}")
if (apkLibPath in existing) zip.deleteEntry(apkLibPath)
zip.writeEntry(apkLibPath, libBytes, ZipCompression.NONE)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ class FetchInfoStep : Step(), KoinComponent {
lateinit var aliuhookVersion: SemVer
private set

/**
* Remote data about the latest Aliuvoice version available from the Aliucord maven.
*/
lateinit var aliuvoiceVersion: SemVer
private set

/**
* Discord build whose libdiscord.so is used as the voice engine.
*/
val libdiscordVersion: Int = 333205

override suspend fun execute(container: StepRunner) {
container.log("Fetching ${AliucordGithubService.DATA_JSON_URL}")
data = github.getBuildData(force = true).getOrThrow()
Expand All @@ -44,5 +55,9 @@ class FetchInfoStep : Step(), KoinComponent {
container.log("Obtaining latest aliuhook version")
aliuhookVersion = maven.getAliuhookVersion(force = true).getOrThrow()
container.log("Fetched aliuhook version: $aliuhookVersion")

container.log("Obtaining latest aliuvoice version")
aliuvoiceVersion = maven.getAliuvoiceVersion(force = true).getOrThrow()
container.log("Fetched aliuvoice version: $aliuvoiceVersion")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ object ManifestPatcher {
private const val REQUEST_LEGACY_EXTERNAL_STORAGE = "requestLegacyExternalStorage"
private const val LABEL = "label"
private const val PACKAGE = "package"
private const val FOREGROUND_SERVICE_TYPE = "foregroundServiceType"
private const val FOREGROUND_SERVICE_TYPES = 0xE0
private const val COMPILE_SDK_VERSION = "compileSdkVersion"
private const val COMPILE_SDK_VERSION_CODENAME = "compileSdkVersionCodename"

Expand Down Expand Up @@ -65,10 +67,14 @@ object ManifestPatcher {
addHorizonOsPerm = false

// Add permissions for Aliucord to work properly for Meta Quest VR headsets
// The VR headsets need the first three, the rest is for API 34, older devices ignore them
for (permission in arrayOf(
"horizonos.permission.HEADSET_CAMERA",
"com.oculus.permission.PLAY_AUDIO_BACKGROUND",
"com.oculus.permission.RECORD_AUDIO_BACKGROUND"
"com.oculus.permission.RECORD_AUDIO_BACKGROUND",
"android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION",
"android.permission.FOREGROUND_SERVICE_MICROPHONE",
"android.permission.FOREGROUND_SERVICE_CAMERA",
)) {
super.child(null, "uses-permission")
.attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, permission)
Expand Down Expand Up @@ -154,6 +160,30 @@ object ManifestPatcher {

return when (name) {
"activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName))
"service" -> object : NodeVisitor(visitor) {
private var foreServiceType = true

override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
if (name == FOREGROUND_SERVICE_TYPE) {
foreServiceType = false
super.attr(ns, name, resourceId, type, (value as Int) or FOREGROUND_SERVICE_TYPES)
} else {
super.attr(ns, name, resourceId, type, value)
}
}

override fun end() {
if (foreServiceType && Build.VERSION.SDK_INT >= 29) super.attr(
ANDROID_NAMESPACE,
FOREGROUND_SERVICE_TYPE,
android.R.attr.foregroundServiceType,
TYPE_FIRST_INT,
FOREGROUND_SERVICE_TYPES
)
super.end()
}
}

"provider" -> object : NodeVisitor(visitor) {
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
super.attr(
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@
<string name="patch_step_restore_cache">Restoring from cache</string>
<string name="patch_step_dl_kt_apk">Downloading Discord APK</string>
<string name="patch_step_dl_kotlin">Downloading Kotlin library</string>
<string name="patch_step_dl_aliuvoice">Downloading Aliuvoice library</string>
<string name="patch_step_dl_libdiscord">Downloading voice engine library</string>
<string name="patch_step_dl_injector">Downloading Aliucord Injector</string>
<string name="patch_step_dl_aliuhook">Downloading Aliuhook library</string>
<string name="patch_step_dl_smali">Downloading smali patches</string>
Expand All @@ -166,6 +168,7 @@
<string name="patch_step_patch_certs">Adding modern root certificates</string>
<string name="patch_step_patch_icon">Patching app icon</string>
<string name="patch_step_add_aliuhook">Adding Aliuhook library</string>
<string name="patch_step_replace_libdiscord">Replacing voice engine</string>
<string name="patch_step_patch_smali">Applying smali patches</string>
<string name="patch_step_reorganize_dex">Adding .dex files to APK</string>
<string name="patch_step_save_metadata">Store metadata</string>
Expand Down
Loading