diff --git a/android/app/build.gradle b/android/app/build.gradle index 2c2a00e87e..0d5aff6d7a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -113,12 +113,9 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.22" // ReVanced - implementation "app.revanced:revanced-patcher:19.1.0" - - // Signing & aligning - implementation("org.bouncycastle:bcpkix-jdk15on:1.70") - implementation("com.android.tools.build:apksig:7.2.2") + implementation "app.revanced:revanced-patcher:19.3.1" + implementation "app.revanced:revanced-library:2.0.0" } diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt index c0fc77a014..6164c9292d 100644 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt +++ b/android/app/src/main/kotlin/app/revanced/manager/flutter/MainActivity.kt @@ -7,23 +7,20 @@ import android.content.pm.PackageInstaller import android.os.Build import android.os.Handler import android.os.Looper +import app.revanced.library.ApkUtils +import app.revanced.library.ApkUtils.applyTo +import app.revanced.library.ApkUtils.sign import app.revanced.manager.flutter.utils.Aapt -import app.revanced.manager.flutter.utils.aligning.ZipAligner import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver -import app.revanced.manager.flutter.utils.signing.Signer -import app.revanced.manager.flutter.utils.zip.ZipFile -import app.revanced.manager.flutter.utils.zip.structures.ZipEntry import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.PatchSet import app.revanced.patcher.Patcher -import app.revanced.patcher.PatcherOptions +import app.revanced.patcher.PatcherConfig import app.revanced.patcher.patch.PatchResult import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.runBlocking import org.json.JSONArray @@ -50,7 +47,10 @@ class MainActivity : FlutterActivity() { val installerChannel = "app.revanced.manager.flutter/installer" val openBrowserChannel = "app.revanced.manager.flutter/browser" - MethodChannel(flutterEngine.dartExecutor.binaryMessenger, openBrowserChannel).setMethodCallHandler { call, result -> + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + openBrowserChannel + ).setMethodCallHandler { call, result -> if (call.method == "openBrowser") { val searchQuery = call.argument("query") openBrowser(searchQuery) @@ -69,40 +69,34 @@ class MainActivity : FlutterActivity() { mainChannel.setMethodCallHandler { call, result -> when (call.method) { "runPatcher" -> { - val originalFilePath = call.argument("originalFilePath") - val inputFilePath = call.argument("inputFilePath") - val patchedFilePath = call.argument("patchedFilePath") + val inFilePath = call.argument("inFilePath") val outFilePath = call.argument("outFilePath") val integrationsPath = call.argument("integrationsPath") val selectedPatches = call.argument>("selectedPatches") val options = call.argument>>("options") - val cacheDirPath = call.argument("cacheDirPath") + val tmpDirPath = call.argument("tmpDirPath") val keyStoreFilePath = call.argument("keyStoreFilePath") val keystorePassword = call.argument("keystorePassword") if ( - originalFilePath != null && - inputFilePath != null && - patchedFilePath != null && + inFilePath != null && outFilePath != null && integrationsPath != null && selectedPatches != null && options != null && - cacheDirPath != null && + tmpDirPath != null && keyStoreFilePath != null && keystorePassword != null ) { cancel = false runPatcher( result, - originalFilePath, - inputFilePath, - patchedFilePath, + inFilePath, outFilePath, integrationsPath, selectedPatches, options, - cacheDirPath, + tmpDirPath, keyStoreFilePath, keystorePassword ) @@ -214,28 +208,23 @@ class MainActivity : FlutterActivity() { startActivity(intent) } } - - @OptIn(InternalCoroutinesApi::class) + private fun runPatcher( result: MethodChannel.Result, - originalFilePath: String, - inputFilePath: String, - patchedFilePath: String, + inFilePath: String, outFilePath: String, integrationsPath: String, selectedPatches: List, options: Map>, - cacheDirPath: String, + tmpDirPath: String, keyStoreFilePath: String, keystorePassword: String ) { - val originalFile = File(originalFilePath) - val inputFile = File(inputFilePath) - val patchedFile = File(patchedFilePath) + val inFile = File(inFilePath) val outFile = File(outFilePath) val integrations = File(integrationsPath) val keyStoreFile = File(keyStoreFilePath) - val cacheDir = File(cacheDirPath) + val tmpDir = File(tmpDirPath) Thread { fun updateProgress(progress: Double, header: String, log: String) { @@ -253,6 +242,16 @@ class MainActivity : FlutterActivity() { fun postStop() = handler.post { stopResult!!.success(null) } + fun cancel(block: () -> Unit = {}): Boolean { + if (cancel) { + block() + postStop() + } + + return cancel + } + + // Setup logger Logger.getLogger("").apply { handlers.forEach { @@ -273,37 +272,19 @@ class MainActivity : FlutterActivity() { } try { - updateProgress(0.0, "", "Copying APK") - - if (cancel) { - postStop() - return@Thread - } - - originalFile.copyTo(inputFile, true) - - if (cancel) { - postStop() - return@Thread - } - - updateProgress(0.05, "Reading APK...", "Reading APK") + updateProgress(0.0, "Reading APK...", "Reading APK") val patcher = Patcher( - PatcherOptions( - inputFile, - cacheDir, + PatcherConfig( + inFile, + tmpDir, Aapt.binary(applicationContext).absolutePath, - cacheDir.path, + tmpDir.path, true // TODO: Add option to disable this ) ) - if (cancel) { - postStop() - return@Thread - } - + if (cancel(patcher::close)) return@Thread updateProgress(0.1, "Loading patches...", "Loading patches") val patches = patches.filter { patch -> @@ -319,32 +300,25 @@ class MainActivity : FlutterActivity() { options[patch.name]?.forEach { (key, value) -> patch.options[key] = value } - } - - if (cancel) { - postStop() - return@Thread - } + }.toSet() + if (cancel(patcher::close)) return@Thread updateProgress(0.15, "Executing...", "") - // Update the progress bar every time a patch is executed from 0.15 to 0.7 - val totalPatchesCount = patches.size - val progressStep = 0.55 / totalPatchesCount - var progress = 0.15 - - patcher.apply { - acceptIntegrations(listOf(integrations)) - acceptPatches(patches) + val patcherResult = patcher.use { + patcher.apply { + acceptIntegrations(setOf(integrations)) + acceptPatches(patches) + } runBlocking { - apply(false).collect(FlowCollector { patchResult: PatchResult -> - if (cancel) { - handler.post { stopResult!!.success(null) } - this.cancel() - this@apply.close() - return@FlowCollector - } + // Update the progress bar every time a patch is executed from 0.15 to 0.7 + val totalPatchesCount = patches.size + val progressStep = 0.55 / totalPatchesCount + var progress = 0.15 + + patcher.apply(false).collect(FlowCollector { patchResult: PatchResult -> + if (cancel(patcher::close)) return@FlowCollector val msg = patchResult.exception?.let { val writer = StringWriter() @@ -358,50 +332,30 @@ class MainActivity : FlutterActivity() { progress += progressStep }) } - } - if (cancel) { - postStop() - patcher.close() - return@Thread - } + if (cancel(patcher::close)) return@Thread + updateProgress(0.75, "Building...", "") - updateProgress(0.75, "Building...", "") + patcher.get() + } - val res = patcher.get() - patcher.close() + inFile.copyTo(outFile) - ZipFile(patchedFile).use { file -> - res.dexFiles.forEach { - if (cancel) { - postStop() - return@Thread - } - file.addEntryCompressData( - ZipEntry.createWithName(it.name), - it.stream.readBytes() - ) - } - res.resourceFile?.let { - file.copyEntriesFromFileAligned( - ZipFile(it), - ZipAligner::getEntryAlignment - ) - } - file.copyEntriesFromFileAligned( - ZipFile(inputFile), - ZipAligner::getEntryAlignment - ) - } + if (cancel(patcher::close)) return@Thread - if (cancel) { - postStop() - return@Thread - } + patcherResult.applyTo(outFile) - updateProgress(0.8, "Signing...", "Signing APK") + if (cancel(patcher::close)) return@Thread + updateProgress(0.8, "Signing...", "") - Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile) + outFile.sign( + ApkUtils.SigningOptions( + keyStoreFile, + keystorePassword, + "alias", + keystorePassword + ) + ) updateProgress(.85, "Patched", "Patched APK") } catch (ex: Throwable) { @@ -421,7 +375,8 @@ class MainActivity : FlutterActivity() { private fun installApk(apkPath: String) { val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller - val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + val sessionParams = + PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) val sessionId: Int = packageInstaller.createSession(sessionParams) val session: PackageInstaller.Session = packageInstaller.openSession(sessionId) session.use { activeSession -> @@ -436,7 +391,12 @@ class MainActivity : FlutterActivity() { val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply { action = "APP_INSTALL_ACTION" } - val receiverPendingIntent = PendingIntent.getBroadcast(context, sessionId, receiverIntent, PackageInstallerManager.flags) + val receiverPendingIntent = PendingIntent.getBroadcast( + context, + sessionId, + receiverIntent, + PackageInstallerManager.flags + ) session.commit(receiverPendingIntent.intentSender) session.close() } @@ -446,7 +406,8 @@ class MainActivity : FlutterActivity() { val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply { action = "APP_UNINSTALL_ACTION" } - val receiverPendingIntent = PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags) + val receiverPendingIntent = + PendingIntent.getBroadcast(context, 0, receiverIntent, PackageInstallerManager.flags) packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender) } diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/aligning/ZipAligner.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/aligning/ZipAligner.kt deleted file mode 100644 index 088aad5993..0000000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/aligning/ZipAligner.kt +++ /dev/null @@ -1,11 +0,0 @@ -package app.revanced.manager.flutter.utils.aligning - -import app.revanced.manager.flutter.utils.zip.structures.ZipEntry - -internal object ZipAligner { - private const val DEFAULT_ALIGNMENT = 4 - private const val LIBRARY_ALIGNMENT = 4096 - - fun getEntryAlignment(entry: ZipEntry): Int? = - if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT -} diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/signing/Signer.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/signing/Signer.kt deleted file mode 100644 index 1e1a08a21c..0000000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/signing/Signer.kt +++ /dev/null @@ -1,74 +0,0 @@ -package app.revanced.manager.flutter.utils.signing - -import com.android.apksig.ApkSigner -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.jce.provider.BouncyCastleProvider -import org.bouncycastle.operator.ContentSigner -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.math.BigInteger -import java.security.* -import java.security.cert.X509Certificate -import java.util.* - -internal class Signer( - private val cn: String, password: String -) { - private val passwordCharArray = password.toCharArray() - private fun newKeystore(out: File) { - val (publicKey, privateKey) = createKey() - val privateKS = KeyStore.getInstance("BKS", "BC") - privateKS.load(null, passwordCharArray) - privateKS.setKeyEntry("alias", privateKey, passwordCharArray, arrayOf(publicKey)) - privateKS.store(FileOutputStream(out), passwordCharArray) - } - - private fun createKey(): Pair { - val gen = KeyPairGenerator.getInstance("RSA") - gen.initialize(2048) - val pair = gen.generateKeyPair() - var serialNumber: BigInteger - do serialNumber = - BigInteger.valueOf(SecureRandom().nextLong()) while (serialNumber < BigInteger.ZERO) - val x500Name = X500Name("CN=$cn") - val builder = X509v3CertificateBuilder( - x500Name, - serialNumber, - Date(System.currentTimeMillis() - 1000L * 60L * 60L * 24L * 30L), - Date(System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 366L * 30L), - Locale.ENGLISH, - x500Name, - SubjectPublicKeyInfo.getInstance(pair.public.encoded) - ) - val signer: ContentSigner = JcaContentSignerBuilder("SHA256withRSA").build(pair.private) - return JcaX509CertificateConverter().getCertificate(builder.build(signer)) to pair.private - } - - fun signApk(input: File, output: File, ks: File) { - Security.addProvider(BouncyCastleProvider()) - - if (!ks.exists()) newKeystore(ks) - - val keyStore = KeyStore.getInstance("BKS", "BC") - FileInputStream(ks).use { fis -> keyStore.load(fis, null) } - val alias = keyStore.aliases().nextElement() - - val config = ApkSigner.SignerConfig.Builder( - cn, - keyStore.getKey(alias, passwordCharArray) as PrivateKey, - listOf(keyStore.getCertificate(alias) as X509Certificate) - ).build() - - val signer = ApkSigner.Builder(listOf(config)) - signer.setCreatedBy(cn) - signer.setInputApk(input) - signer.setOutputApk(output) - - signer.build().sign() - } -} diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/Extensions.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/Extensions.kt deleted file mode 100644 index 3ff0516de5..0000000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/Extensions.kt +++ /dev/null @@ -1,35 +0,0 @@ -@file:Suppress("unused") - -package app.revanced.manager.flutter.utils.zip - -import java.io.DataInput -import java.io.DataOutput -import java.nio.ByteBuffer - -fun UInt.toLittleEndian() = - (((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt() - -fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort() - -fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8) - or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt() - -fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort() - -fun ByteBuffer.getUShort() = this.short.toUShort() -fun ByteBuffer.getUInt() = this.int.toUInt() - -fun ByteBuffer.putUShort(ushort: UShort): ByteBuffer = this.putShort(ushort.toShort()) -fun ByteBuffer.putUInt(uint: UInt): ByteBuffer = this.putInt(uint.toInt()) - -fun DataInput.readUShort() = this.readShort().toUShort() -fun DataInput.readUInt() = this.readInt().toUInt() - -fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt()) -fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt()) - -fun DataInput.readUShortLE() = this.readUShort().toBigEndian() -fun DataInput.readUIntLE() = this.readUInt().toBigEndian() - -fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian()) -fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian()) diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/ZipFile.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/ZipFile.kt deleted file mode 100644 index 2330938b3b..0000000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/ZipFile.kt +++ /dev/null @@ -1,176 +0,0 @@ -package app.revanced.manager.flutter.utils.zip - -import app.revanced.manager.flutter.utils.zip.structures.ZipEndRecord -import app.revanced.manager.flutter.utils.zip.structures.ZipEntry -import java.io.Closeable -import java.io.File -import java.io.RandomAccessFile -import java.nio.ByteBuffer -import java.nio.channels.FileChannel -import java.util.zip.CRC32 -import java.util.zip.Deflater - -class ZipFile(file: File) : Closeable { - var entries: MutableList = mutableListOf() - - private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw") - private var CDNeedsRewrite = false - - private val compressionLevel = 5 - - init { - //if file isn't empty try to load entries - if (file.length() > 0) { - val endRecord = findEndRecord() - - if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries) - throw IllegalArgumentException("Multi-file archives are not supported") - - entries = readEntries(endRecord).toMutableList() - } - - //seek back to start for writing - filePointer.seek(0) - } - - private fun findEndRecord(): ZipEndRecord { - //look from end to start since end record is at the end - for (i in filePointer.length() - 1 downTo 0) { - filePointer.seek(i) - //possible beginning of signature - if (filePointer.readByte() == 0x50.toByte()) { - //seek back to get the full int - filePointer.seek(i) - val possibleSignature = filePointer.readUIntLE() - if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) { - filePointer.seek(i) - return ZipEndRecord.fromECD(filePointer) - } - } - } - - throw Exception("Couldn't find end record") - } - - private fun readEntries(endRecord: ZipEndRecord): List { - filePointer.seek(endRecord.centralDirectoryStartOffset.toLong()) - - val numberOfEntries = endRecord.diskEntries.toInt() - - return buildList(numberOfEntries) { - for (i in 1..numberOfEntries) { - add( - ZipEntry.fromCDE(filePointer).also - { - //for some reason the local extra field can be different from the central one - it.readLocalExtra( - filePointer.channel.map( - FileChannel.MapMode.READ_ONLY, - it.localHeaderOffset.toLong() + 28, - 2 - ) - ) - }) - } - } - } - - private fun writeCD() { - val CDStart = filePointer.channel.position().toUInt() - - entries.forEach { - filePointer.channel.write(it.toCDE()) - } - - val entriesCount = entries.size.toUShort() - - val endRecord = ZipEndRecord( - 0u, - 0u, - entriesCount, - entriesCount, - filePointer.channel.position().toUInt() - CDStart, - CDStart, - "" - ) - - filePointer.channel.write(endRecord.toECD()) - } - - private fun addEntry(entry: ZipEntry, data: ByteBuffer) { - CDNeedsRewrite = true - - entry.localHeaderOffset = filePointer.channel.position().toUInt() - - filePointer.channel.write(entry.toLFH()) - filePointer.channel.write(data) - - entries.add(entry) - } - - fun addEntryCompressData(entry: ZipEntry, data: ByteArray) { - val compressor = Deflater(compressionLevel, true) - compressor.setInput(data) - compressor.finish() - - val uncompressedSize = data.size - val compressedData = - ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger - - val compressedDataLength = compressor.deflate(compressedData) - val compressedBuffer = - ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray()) - - compressor.end() - - val crc = CRC32() - crc.update(data) - - entry.compression = 8u //deflate compression - entry.uncompressedSize = uncompressedSize.toUInt() - entry.compressedSize = compressedDataLength.toUInt() - entry.crc32 = crc.value.toUInt() - - addEntry(entry, compressedBuffer) - } - - private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) { - alignment?.let { - //calculate where data would end up - val dataOffset = filePointer.filePointer + entry.LFHSize - - val mod = dataOffset % alignment - - //wrong alignment - if (mod != 0L) { - //add padding at end of extra field - entry.localExtraField = - entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt()) - } - } - - addEntry(entry, data) - } - - fun getDataForEntry(entry: ZipEntry): ByteBuffer { - return filePointer.channel.map( - FileChannel.MapMode.READ_ONLY, - entry.dataOffset.toLong(), - entry.compressedSize.toLong() - ) - } - - fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) { - for (entry in file.entries) { - if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates - - val data = file.getDataForEntry(entry) - addEntryCopyData(entry, data, entryAlignment(entry)) - } - } - - override fun close() { - if (CDNeedsRewrite) writeCD() - filePointer.close() - } -} diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/structures/ZipEndRecord.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/structures/ZipEndRecord.kt deleted file mode 100644 index e7b9b58e26..0000000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/structures/ZipEndRecord.kt +++ /dev/null @@ -1,78 +0,0 @@ -package app.revanced.manager.flutter.utils.zip.structures - -import app.revanced.manager.flutter.utils.zip.putUInt -import app.revanced.manager.flutter.utils.zip.putUShort -import app.revanced.manager.flutter.utils.zip.readUIntLE -import app.revanced.manager.flutter.utils.zip.readUShortLE -import java.io.DataInput -import java.nio.ByteBuffer -import java.nio.ByteOrder - -data class ZipEndRecord( - val diskNumber: UShort, - val startingDiskNumber: UShort, - val diskEntries: UShort, - val totalEntries: UShort, - val centralDirectorySize: UInt, - val centralDirectoryStartOffset: UInt, - val fileComment: String, -) { - - companion object { - const val ECD_HEADER_SIZE = 22 - const val ECD_SIGNATURE = 0x06054b50u - - fun fromECD(input: DataInput): ZipEndRecord { - val signature = input.readUIntLE() - - if (signature != ECD_SIGNATURE) - throw IllegalArgumentException("Input doesn't start with end record signature") - - val diskNumber = input.readUShortLE() - val startingDiskNumber = input.readUShortLE() - val diskEntries = input.readUShortLE() - val totalEntries = input.readUShortLE() - val centralDirectorySize = input.readUIntLE() - val centralDirectoryStartOffset = input.readUIntLE() - val fileCommentLength = input.readUShortLE() - var fileComment = "" - - if (fileCommentLength > 0u) { - val fileCommentBytes = ByteArray(fileCommentLength.toInt()) - input.readFully(fileCommentBytes) - fileComment = fileCommentBytes.toString(Charsets.UTF_8) - } - - return ZipEndRecord( - diskNumber, - startingDiskNumber, - diskEntries, - totalEntries, - centralDirectorySize, - centralDirectoryStartOffset, - fileComment - ) - } - } - - fun toECD(): ByteBuffer { - val commentBytes = fileComment.toByteArray(Charsets.UTF_8) - - val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size) - .also { it.order(ByteOrder.LITTLE_ENDIAN) } - - buffer.putUInt(ECD_SIGNATURE) - buffer.putUShort(diskNumber) - buffer.putUShort(startingDiskNumber) - buffer.putUShort(diskEntries) - buffer.putUShort(totalEntries) - buffer.putUInt(centralDirectorySize) - buffer.putUInt(centralDirectoryStartOffset) - buffer.putUShort(commentBytes.size.toUShort()) - - buffer.put(commentBytes) - - buffer.flip() - return buffer - } -} diff --git a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/structures/ZipEntry.kt b/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/structures/ZipEntry.kt deleted file mode 100644 index bda1398e7c..0000000000 --- a/android/app/src/main/kotlin/app/revanced/manager/flutter/utils/zip/structures/ZipEntry.kt +++ /dev/null @@ -1,190 +0,0 @@ -package app.revanced.manager.flutter.utils.zip.structures - -import app.revanced.manager.flutter.utils.zip.* -import java.io.DataInput -import java.nio.ByteBuffer -import java.nio.ByteOrder - -data class ZipEntry( - val version: UShort, - val versionNeeded: UShort, - val flags: UShort, - var compression: UShort, - val modificationTime: UShort, - val modificationDate: UShort, - var crc32: UInt, - var compressedSize: UInt, - var uncompressedSize: UInt, - val diskNumber: UShort, - val internalAttributes: UShort, - val externalAttributes: UInt, - var localHeaderOffset: UInt, - val fileName: String, - val extraField: ByteArray, - val fileComment: String, - var localExtraField: ByteArray = ByteArray(0), //separate for alignment -) { - val LFHSize: Int - get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size - - val dataOffset: UInt - get() = localHeaderOffset + LFHSize.toUInt() - - companion object { - const val CDE_HEADER_SIZE = 46 - const val CDE_SIGNATURE = 0x02014b50u - - const val LFH_HEADER_SIZE = 30 - const val LFH_SIGNATURE = 0x04034b50u - - fun createWithName(fileName: String): ZipEntry { - return ZipEntry( - 0x1403u, //made by unix, version 20 - 0u, - 0u, - 0u, - 0x0821u, //seems to be static time google uses, no idea - 0x0221u, //same as above - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - 0u, - fileName, - ByteArray(0), - "" - ) - } - - fun fromCDE(input: DataInput): ZipEntry { - val signature = input.readUIntLE() - - if (signature != CDE_SIGNATURE) - throw IllegalArgumentException("Input doesn't start with central directory entry signature") - - val version = input.readUShortLE() - val versionNeeded = input.readUShortLE() - var flags = input.readUShortLE() - val compression = input.readUShortLE() - val modificationTime = input.readUShortLE() - val modificationDate = input.readUShortLE() - val crc32 = input.readUIntLE() - val compressedSize = input.readUIntLE() - val uncompressedSize = input.readUIntLE() - val fileNameLength = input.readUShortLE() - var fileName = "" - val extraFieldLength = input.readUShortLE() - val extraField = ByteArray(extraFieldLength.toInt()) - val fileCommentLength = input.readUShortLE() - var fileComment = "" - val diskNumber = input.readUShortLE() - val internalAttributes = input.readUShortLE() - val externalAttributes = input.readUIntLE() - val localHeaderOffset = input.readUIntLE() - - val variableFieldsLength = - fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt() - - if (variableFieldsLength > 0) { - val fileNameBytes = ByteArray(fileNameLength.toInt()) - input.readFully(fileNameBytes) - fileName = fileNameBytes.toString(Charsets.UTF_8) - - input.readFully(extraField) - - val fileCommentBytes = ByteArray(fileCommentLength.toInt()) - input.readFully(fileCommentBytes) - fileComment = fileCommentBytes.toString(Charsets.UTF_8) - } - - flags = (flags and 0b1000u.inv() - .toUShort()) //disable data descriptor flag as they are not used - - return ZipEntry( - version, - versionNeeded, - flags, - compression, - modificationTime, - modificationDate, - crc32, - compressedSize, - uncompressedSize, - diskNumber, - internalAttributes, - externalAttributes, - localHeaderOffset, - fileName, - extraField, - fileComment, - ) - } - } - - fun readLocalExtra(buffer: ByteBuffer) { - buffer.order(ByteOrder.LITTLE_ENDIAN) - localExtraField = ByteArray(buffer.getUShort().toInt()) - } - - fun toLFH(): ByteBuffer { - val nameBytes = fileName.toByteArray(Charsets.UTF_8) - - val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size) - .also { it.order(ByteOrder.LITTLE_ENDIAN) } - - buffer.putUInt(LFH_SIGNATURE) - buffer.putUShort(versionNeeded) - buffer.putUShort(flags) - buffer.putUShort(compression) - buffer.putUShort(modificationTime) - buffer.putUShort(modificationDate) - buffer.putUInt(crc32) - buffer.putUInt(compressedSize) - buffer.putUInt(uncompressedSize) - buffer.putUShort(nameBytes.size.toUShort()) - buffer.putUShort(localExtraField.size.toUShort()) - - buffer.put(nameBytes) - buffer.put(localExtraField) - - buffer.flip() - return buffer - } - - fun toCDE(): ByteBuffer { - val nameBytes = fileName.toByteArray(Charsets.UTF_8) - val commentBytes = fileComment.toByteArray(Charsets.UTF_8) - - val buffer = - ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size) - .also { it.order(ByteOrder.LITTLE_ENDIAN) } - - buffer.putUInt(CDE_SIGNATURE) - buffer.putUShort(version) - buffer.putUShort(versionNeeded) - buffer.putUShort(flags) - buffer.putUShort(compression) - buffer.putUShort(modificationTime) - buffer.putUShort(modificationDate) - buffer.putUInt(crc32) - buffer.putUInt(compressedSize) - buffer.putUInt(uncompressedSize) - buffer.putUShort(nameBytes.size.toUShort()) - buffer.putUShort(extraField.size.toUShort()) - buffer.putUShort(commentBytes.size.toUShort()) - buffer.putUShort(diskNumber) - buffer.putUShort(internalAttributes) - buffer.putUInt(externalAttributes) - buffer.putUInt(localHeaderOffset) - - buffer.put(nameBytes) - buffer.put(extraField) - buffer.put(commentBytes) - - buffer.flip() - return buffer - } -} - diff --git a/android/build.gradle b/android/build.gradle index 4b91286566..75ee206042 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,7 +3,12 @@ allprojects { google() mavenCentral() maven { - url 'https://jitpack.io' + // A repository must be speficied for some reason. "registry" is a dummy. + url = uri("https://maven.pkg.github.com/revanced/registry") + credentials { + username = project.findProperty("gpr.user") as String ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") as String ?: System.getenv("GITHUB_TOKEN") + } } mavenLocal() } diff --git a/docs/4_building.md b/docs/4_building.md index e9d14a4aef..de67fad681 100644 --- a/docs/4_building.md +++ b/docs/4_building.md @@ -28,3 +28,14 @@ Learn how to build ReVanced Manager from source. ```sh flutter build apk ``` + +> [!NOTE] +> If the build fails due to authentication, you may need to authenticate to GitHub Packages. +> Create a PAT with the scope `read:packages` [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced) and add your token to ~/.gradle/gradle.properties. +> +> Example `gradle.properties` file: +> +> ```properties +> gpr.user = user +> gpr.key = key +> ``` diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index edd4bb5b30..7d45a8bb63 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -172,25 +172,22 @@ class PatcherAPI { _dataDir.createSync(); _tmpDir.createSync(); final Directory workDir = _tmpDir.createTempSync('tmp-'); - final File inputFile = File('${workDir.path}/base.apk'); - final File patchedFile = File('${workDir.path}/patched.apk'); + outFile = File('${workDir.path}/out.apk'); - final Directory cacheDir = Directory('${workDir.path}/cache'); - cacheDir.createSync(); - final String originalFilePath = apkFilePath; + + final Directory tmpDir = + Directory('${workDir.path}/revanced-temporary-files'); try { await patcherChannel.invokeMethod( 'runPatcher', { - 'originalFilePath': originalFilePath, - 'inputFilePath': inputFile.path, - 'patchedFilePath': patchedFile.path, + 'inFilePath': apkFilePath, 'outFilePath': outFile!.path, 'integrationsPath': integrationsFile.path, 'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'options': options, - 'cacheDirPath': cacheDir.path, + 'tmpDirPath': tmpDir.path, 'keyStoreFilePath': _keyStoreFile.path, 'keystorePassword': _managerAPI.getKeystorePassword(), }, @@ -462,7 +459,6 @@ enum InstallStatus { mountNoRoot(1), mountVersionMismatch(1.1), mountMissingInstallation(1.2), - statusFailureBlocked(2), installFailedVerificationFailure(3.1), statusFailureInvalid(4), @@ -473,6 +469,7 @@ enum InstallStatus { statusFailureTimeout(8); const InstallStatus(this.statusCode); + final double statusCode; static String byCode(num code) { diff --git a/lib/ui/theme/dynamic_theme_builder.dart b/lib/ui/theme/dynamic_theme_builder.dart index 876a6d23c7..5ee0a22212 100644 --- a/lib/ui/theme/dynamic_theme_builder.dart +++ b/lib/ui/theme/dynamic_theme_builder.dart @@ -2,13 +2,10 @@ import 'dart:ui'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.router.dart'; import 'package:revanced_manager/gen/strings.g.dart'; -import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/theme.dart'; import 'package:stacked_services/stacked_services.dart';