diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e1d869df..b2d1fd69 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,7 +18,7 @@ android { compileSdk = 35 defaultConfig { - minSdk = 24 + minSdk = 21 targetSdk = 35 versionCode = 1 versionName = "0.0.1" @@ -91,21 +91,24 @@ android { excludes += "/org/bouncycastle/**" } jniLibs { + // For extractNativeLibs=false + useLegacyPackaging = false + // x86 is dead excludes += "/lib/x86/*.so" } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { val reportsDir = layout.buildDirectory.asFile.get() .resolve("reports").absolutePath - jvmTarget = "11" + jvmTarget = "1.8" freeCompilerArgs += listOf( "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", diff --git a/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt b/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt index 21c6efee..81b3d8a8 100644 --- a/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt +++ b/app/src/main/kotlin/com/aliucord/manager/di/DownloadManagerProvider.kt @@ -2,15 +2,15 @@ package com.aliucord.manager.di import com.aliucord.manager.manager.PreferencesManager import com.aliucord.manager.manager.download.* -import com.aliucord.manager.patcher.util.Signer.getKoin import com.aliucord.manager.util.IS_PROBABLY_EMULATOR import org.koin.core.annotation.KoinInternalApi +import org.koin.core.component.KoinComponent import kotlin.reflect.KClass /** * Handle providing the correct install manager based on preferences and device type. */ -class DownloadManagerProvider(private val prefs: PreferencesManager) { +class DownloadManagerProvider(private val prefs: PreferencesManager) : KoinComponent { fun getActiveDownloader(): IDownloadManager = getDownloader(prefs.downloader) diff --git a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt index aca9ed75..f9c58502 100644 --- a/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt +++ b/app/src/main/kotlin/com/aliucord/manager/manager/PathManager.kt @@ -2,6 +2,8 @@ package com.aliucord.manager.manager import android.content.Context import android.os.Environment +import android.util.Log +import com.aliucord.manager.BuildConfig import com.aliucord.manager.network.utils.SemVer import java.io.File @@ -30,23 +32,27 @@ class PathManager(context: Context) { */ val keystoreFile = aliucordDir.resolve("ks.keystore") - private val externalCacheDir = context.externalCacheDir - ?: throw Error("External cache directory isn't supported") + private val cacheDir = run { + if (context.externalCacheDir == null) + Log.i(BuildConfig.TAG, "No external cache directory, resorting to internal cache!") + + context.externalCacheDir ?: context.cacheDir + } /** * Standard path: `~/Android/data/com.aliucord.manager/cache` */ - private val discordApkCache = externalCacheDir + private val discordApkCache = cacheDir .resolve("discord") /** * Delete the entire cache dir and recreate it. */ fun clearCache() { - if (!externalCacheDir.deleteRecursively()) + if (!cacheDir.deleteRecursively()) throw IllegalStateException("Failed to delete cache") - externalCacheDir.mkdirs() + cacheDir.mkdirs() } /** @@ -59,45 +65,45 @@ class PathManager(context: Context) { /** * Resolve a specific path for a cached injector. */ - fun cachedInjectorDex(version: SemVer, custom: Boolean = false) = externalCacheDir + fun cachedInjectorDex(version: SemVer, custom: Boolean = false) = cacheDir .resolve("injector").apply { mkdirs() } .resolve("$version${if (custom) ".custom" else ""}.dex") /** * Get all the versions of custom injector builds. */ - fun customInjectorDexs() = listCustomFiles(externalCacheDir.resolve("injector")) + fun customInjectorDexs() = listCustomFiles(cacheDir.resolve("injector")) /** * Resolve a specific path for a versioned cached Aliuhook build */ - fun cachedAliuhookAAR(version: String) = externalCacheDir + fun cachedAliuhookAAR(version: String) = cacheDir .resolve("aliuhook").apply { mkdirs() } .resolve("$version.aar") /** * Resolve a specific path for a versioned smali patches archive. */ - fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = externalCacheDir + fun cachedSmaliPatches(version: SemVer, custom: Boolean = false) = cacheDir .resolve("patches").apply { mkdirs() } .resolve("$version${if (custom) ".custom" else ""}.zip") /** * Get all the versions of custom smali bundles. */ - fun customSmaliPatches() = listCustomFiles(externalCacheDir.resolve("patches")) + fun customSmaliPatches() = listCustomFiles(cacheDir.resolve("patches")) /** * Singular Kotlin file of the most up-to-date version * since the stdlib is backwards compatible. */ - fun cachedKotlinDex() = externalCacheDir + fun cachedKotlinDex() = cacheDir .resolve("kotlin.dex") /** * The temporary working directory of a currently executing patching process. */ - fun patchingWorkingDir() = externalCacheDir + fun patchingWorkingDir() = cacheDir .resolve("patched") private companion object { diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/signing/ApkSigSigner.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/signing/ApkSigSigner.kt new file mode 100644 index 00000000..49e7e121 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/signing/ApkSigSigner.kt @@ -0,0 +1,101 @@ +package com.aliucord.manager.patcher.signing + +import com.aliucord.manager.manager.PathManager +import com.android.apksig.ApkSigner +import com.android.apksig.KeyConfig +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import org.bouncycastle.cert.X509v3CertificateBuilder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import java.io.File +import java.math.BigInteger +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.PrivateKey +import java.security.SecureRandom +import java.security.cert.Certificate +import java.security.cert.X509Certificate +import java.util.Date +import java.util.Locale + +object ApkSigSigner : KoinComponent { + private val paths = get() + private val password = "password".toCharArray() + + private val signerConfig: ApkSigner.SignerConfig by lazy { + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) + + // Create new keystore if it doesn't exist + if (!paths.keystoreFile.exists()) { + paths.aliucordDir.mkdirs() + newKeystore(paths.keystoreFile) + } + + paths.keystoreFile.inputStream() + .use { keyStore.load(it, /* password = */ null) } + + val alias = keyStore.aliases().nextElement() + val certificate = keyStore.getCertificate(alias) as X509Certificate + + ApkSigner.SignerConfig.Builder( + "Aliucord Manager signer", + KeyConfig.Jca(keyStore.getKey(alias, password) as PrivateKey), + listOf(certificate) + ).build() + } + + private fun newKeystore(out: File) { + val key = createKey() + + with(KeyStore.getInstance(KeyStore.getDefaultType())) { + load(null, password) + setKeyEntry("alias", key.privateKey, password, arrayOf(key.publicKey)) + store(out.outputStream(), password) + } + } + + private fun createKey(): KeySet { + var serialNumber: BigInteger + + do serialNumber = SecureRandom().nextInt().toBigInteger() + while (serialNumber < BigInteger.ZERO) + + val x500Name = X500Name("CN=Aliucord Manager") + val pair = KeyPairGenerator.getInstance("RSA").run { + initialize(2048) + generateKeyPair() + } + val builder = X509v3CertificateBuilder( + /* issuer = */ x500Name, + /* serial = */ serialNumber, + /* notBefore = */ Date(0), + /* notAfter = */ Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L), // 30 years + /* dateLocale = */ Locale.ENGLISH, + /* subject = */ x500Name, + /* publicKeyInfo = */ SubjectPublicKeyInfo.getInstance(pair.public.encoded) + ) + val signer = JcaContentSignerBuilder("SHA1withRSA").build(pair.private) + + return KeySet(JcaX509CertificateConverter().getCertificate(builder.build(signer)), pair.private) + } + + fun signApk(apkFile: File) { + val tmpApk = apkFile.resolveSibling(apkFile.name + ".tmp") + + ApkSigner.Builder(listOf(signerConfig)) + .setV1SigningEnabled(false) // TODO: enable so api <24 devices can work, however zip-alignment breaks + .setV2SigningEnabled(true) + .setV3SigningEnabled(true) + .setInputApk(apkFile) + .setOutputApk(tmpApk) + .build() + .sign() + + tmpApk.renameTo(apkFile) + } + + private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/signing/KeySet.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/signing/KeySet.kt new file mode 100644 index 00000000..aa1669f7 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/signing/KeySet.kt @@ -0,0 +1,9 @@ +package com.aliucord.manager.patcher.signing + +import java.security.PrivateKey +import java.security.cert.X509Certificate + +data class KeySet( + val publicKey: X509Certificate, + val privateKey: PrivateKey, +) diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/SigningStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/SigningStep.kt index 834d4686..bfc6500d 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/SigningStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/install/SigningStep.kt @@ -2,10 +2,10 @@ package com.aliucord.manager.patcher.steps.install import com.aliucord.manager.R import com.aliucord.manager.patcher.StepRunner +import com.aliucord.manager.patcher.signing.ApkSigSigner 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.util.Signer import org.koin.core.component.KoinComponent /** @@ -18,6 +18,6 @@ class SigningStep : Step(), KoinComponent { override suspend fun execute(container: StepRunner) { val apk = container.getStep().patchedApk - Signer.signApk(apk) + ApkSigSigner.signApk(apk) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/AddAliuhookLibsStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/AddAliuhookLibsStep.kt index eb80f425..f35f4d9c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/AddAliuhookLibsStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/AddAliuhookLibsStep.kt @@ -8,6 +8,7 @@ import com.aliucord.manager.patcher.steps.base.IDexProvider import com.aliucord.manager.patcher.steps.base.Step import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep import com.aliucord.manager.patcher.steps.download.DownloadAliuhookStep +import com.github.diamondminer88.zip.ZipCompression import com.github.diamondminer88.zip.ZipReader import com.github.diamondminer88.zip.ZipWriter import org.koin.core.component.KoinComponent @@ -31,7 +32,7 @@ class AddAliuhookLibsStep : Step(), KoinComponent { val bytes = aliuhook.openEntry("jni/$currentDeviceArch/$libFile")?.read() ?: throw IllegalStateException("Failed to read $libFile from aliuhook aar") - patchedApk.writeEntry("lib/$currentDeviceArch/$libFile", bytes) + patchedApk.writeEntry("lib/$currentDeviceArch/$libFile", bytes, ZipCompression.NONE) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/ReorganizeDexStep.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/ReorganizeDexStep.kt index 0bd23bb0..9479b20e 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/ReorganizeDexStep.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/steps/patch/ReorganizeDexStep.kt @@ -7,6 +7,7 @@ import com.aliucord.manager.patcher.steps.StepGroup import com.aliucord.manager.patcher.steps.base.IDexProvider import com.aliucord.manager.patcher.steps.base.Step import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep +import com.github.diamondminer88.zip.ZipCompression import com.github.diamondminer88.zip.ZipReader import com.github.diamondminer88.zip.ZipWriter import org.koin.core.component.KoinComponent @@ -59,7 +60,7 @@ class ReorganizeDexStep : Step(), KoinComponent { if (dexProvider.dexPriority <= 0) continue for (dexBytes in dexProvider.getDexFiles()) { - zip.writeEntry(getDexName(idx++), dexBytes) + zip.writeEntry(getDexName(idx++), dexBytes, ZipCompression.NONE) } } @@ -70,7 +71,7 @@ class ReorganizeDexStep : Step(), KoinComponent { val file = paths.patchingWorkingDir().resolve(getDexName(idx)) val bytes = file.readBytes() - zip.writeEntry(getDexName(dexCount + idx), bytes) + zip.writeEntry(getDexName(dexCount + idx), bytes, ZipCompression.NONE) } dexCount += idx @@ -80,7 +81,7 @@ class ReorganizeDexStep : Step(), KoinComponent { if (dexProvider.dexPriority > 0) continue for (dexBytes in dexProvider.getDexFiles()) { - zip.writeEntry(getDexName(dexCount++), dexBytes) + zip.writeEntry(getDexName(dexCount++), dexBytes, ZipCompression.NONE) } } } diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt index 45bbfd29..5e085638 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/util/ManifestPatcher.kt @@ -55,7 +55,7 @@ object ManifestPatcher { val nv = super.child(ns, name) // Add MANAGE_EXTERNAL_STORAGE when necessary - if (addExternalStoragePerm) { + if (addExternalStoragePerm && Build.VERSION.SDK_INT >= 30) { super .child(null, "uses-permission") .attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, Manifest.permission.MANAGE_EXTERNAL_STORAGE) @@ -76,16 +76,12 @@ object ManifestPatcher { } } - "uses-sdk" -> object : NodeVisitor(nv) { - override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) { - if (name == "targetSdkVersion") { - val version = if (Build.VERSION.SDK_INT >= 31) 30 else 28 - super.attr(ns, name, resourceId, type, version) - } else { - super.attr(ns, name, resourceId, type, value) - } - } - } + "uses-sdk" -> ReplaceAttrsVisitor( + nv, + mapOf( + "targetSdkVersion" to if (Build.VERSION.SDK_INT >= 31) 30 else 28, + ) + ) "application" -> object : ReplaceAttrsVisitor( nv, @@ -158,7 +154,7 @@ object ManifestPatcher { super.attr(ANDROID_NAMESPACE, VM_SAFE_MODE, android.R.attr.vmSafeMode, TYPE_INT_BOOLEAN, 1) } - if (addExtractNativeLibs) super.attr( + if (addExtractNativeLibs && Build.VERSION.SDK_INT >= 23) super.attr( ANDROID_NAMESPACE, EXTRACT_NATIVE_LIBS, android.R.attr.extractNativeLibs, @@ -179,24 +175,6 @@ object ManifestPatcher { return writer.toByteArray() } - fun renamePackage( - manifestBytes: ByteArray, - packageName: String, - ): ByteArray { - val reader = AxmlReader(manifestBytes) - val writer = AxmlWriter() - - reader.accept( - object : AxmlVisitor(writer) { - override fun child(ns: String?, name: String?): ReplaceAttrsVisitor { - return ReplaceAttrsVisitor(super.child(ns, name), mapOf("package" to packageName)) - } - } - ) - - return writer.toByteArray() - } - private open class ReplaceAttrsVisitor( nv: NodeVisitor, private val attrs: Map, diff --git a/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt b/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt index f8bf9b00..533f736f 100644 --- a/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt +++ b/app/src/main/kotlin/com/aliucord/manager/patcher/util/Signer.kt @@ -23,81 +23,4 @@ import java.security.cert.X509Certificate import java.util.Date import java.util.Locale -object Signer : KoinComponent { - private val paths = get() - private val password = "password".toCharArray() - private val signerConfig: ApkSigner.SignerConfig by lazy { - val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) - - // Create new keystore if it doesn't exist - if (!paths.keystoreFile.exists()) { - paths.aliucordDir.mkdirs() - newKeystore(paths.keystoreFile) - } - - paths.keystoreFile.inputStream() - .use { keyStore.load(it, /* password = */ null) } - - val alias = keyStore.aliases().nextElement() - val certificate = keyStore.getCertificate(alias) as X509Certificate - - ApkSigner.SignerConfig.Builder( - "Aliucord Manager signer", - KeyConfig.Jca(keyStore.getKey(alias, password) as PrivateKey), - listOf(certificate) - ).build() - } - - private fun newKeystore(out: File) { - val key = createKey() - - with(KeyStore.getInstance(KeyStore.getDefaultType())) { - load(null, password) - setKeyEntry("alias", key.privateKey, password, arrayOf(key.publicKey)) - store(out.outputStream(), password) - } - } - - private fun createKey(): KeySet { - var serialNumber: BigInteger - - do serialNumber = SecureRandom().nextInt().toBigInteger() - while (serialNumber < BigInteger.ZERO) - - val x500Name = X500Name("CN=Aliucord Manager") - val pair = KeyPairGenerator.getInstance("RSA").run { - initialize(2048) - generateKeyPair() - } - val builder = X509v3CertificateBuilder( - /* issuer = */ x500Name, - /* serial = */ serialNumber, - /* notBefore = */ Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L), - /* notAfter = */ Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L), - /* dateLocale = */ Locale.ENGLISH, - /* subject = */ x500Name, - /* publicKeyInfo = */ SubjectPublicKeyInfo.getInstance(pair.public.encoded) - ) - val signer = JcaContentSignerBuilder("SHA1withRSA").build(pair.private) - - return KeySet(JcaX509CertificateConverter().getCertificate(builder.build(signer)), pair.private) - } - - fun signApk(apkFile: File) { - val tmpApk = apkFile.resolveSibling(apkFile.name + ".tmp") - - ApkSigner.Builder(listOf(signerConfig)) - .setV1SigningEnabled(false) // TODO: enable so api <24 devices can work, however zip-alignment breaks - .setV2SigningEnabled(true) - .setV3SigningEnabled(true) - .setInputApk(apkFile) - .setOutputApk(tmpApk) - .build() - .sign() - - tmpApk.renameTo(apkFile) - } - - private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey) -} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt index adae9872..b45e0117 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/home/HomeScreen.kt @@ -6,6 +6,7 @@ package com.aliucord.manager.ui.screens.home +import android.os.Build import androidx.compose.animation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -215,7 +216,13 @@ fun NoInstallsContent( .padding(bottom = 80.dp) ) { Text( - text = """ /ᐠﹷ ‸ ﹷ ᐟ\ノ""", + text = if (Build.VERSION.SDK_INT >= 23) { + """ /ᐠﹷ ‸ ﹷ ᐟ\ノ""" + } else { + // Removed U+141F and U+1420 (not rendered on API <23) + // May look weird in code editor but is correct + "/ ﹷ ‸ ﹷ \\ノ" + }, style = MaterialTheme.typography.labelLarge .copy(fontSize = 38.sp), ) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt index 2b0d962d..63ccdf12 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patching/PatchingScreenModel.kt @@ -166,7 +166,7 @@ class PatchingScreenModel( Android API: ${Build.VERSION.SDK_INT} Emulator: $IS_PROBABLY_EMULATOR (guess) - ROM: Android ${Build.VERSION.RELEASE} (Patch ${Build.VERSION.SECURITY_PATCH}) + ROM: Android ${Build.VERSION.RELEASE} (Patch ${if (Build.VERSION.SDK_INT >= 23) Build.VERSION.SECURITY_PATCH else ""}) Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()} Device: ${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE}) Play Protect: $playProtect diff --git a/app/src/main/kotlin/com/aliucord/manager/util/Context.kt b/app/src/main/kotlin/com/aliucord/manager/util/Context.kt index 3b6ed70f..57620f4c 100644 --- a/app/src/main/kotlin/com/aliucord/manager/util/Context.kt +++ b/app/src/main/kotlin/com/aliucord/manager/util/Context.kt @@ -75,6 +75,8 @@ fun Context.findActivity(): Activity? { } fun Context.isIgnoringBatteryOptimizations(): Boolean { + if (Build.VERSION.SDK_INT < 23) return false + val power = applicationContext.getSystemService(PowerManager::class.java) val name = applicationContext.packageName if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { @@ -88,6 +90,8 @@ fun Context.isIgnoringBatteryOptimizations(): Boolean { */ @SuppressLint("BatteryLife") fun Context.requestNoBatteryOptimizations() { + if (Build.VERSION.SDK_INT < 23) return + val intent = Intent( /* action = */ Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, /* uri = */ Uri.fromParts("package", this.packageName, null) @@ -103,7 +107,7 @@ fun Context.requestNoBatteryOptimizations() { startActivity(intent) } -/* +/** * Get the raw bytes for a resource. * @param id The resource identifier * @return The resource's raw bytes as stored inside the APK diff --git a/settings.gradle.kts b/settings.gradle.kts index 4422588e..52f702f1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ pluginManagement { dependencyResolutionManagement { repositories { + mavenLocal() google() mavenCentral() maven {