build(Needs bump): Bump dependencies (#1717)

This commit is contained in:
oSumAtrIX 2024-03-04 12:47:38 +01:00 committed by GitHub
parent 6dee3aa1b7
commit 10bae69db6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 104 additions and 700 deletions

View File

@ -113,12 +113,9 @@ flutter {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.22"
// ReVanced // ReVanced
implementation "app.revanced:revanced-patcher:19.1.0" implementation "app.revanced:revanced-patcher:19.3.1"
implementation "app.revanced:revanced-library:2.0.0"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2")
} }

View File

@ -7,23 +7,20 @@ import android.content.pm.PackageInstaller
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper 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.Aapt
import app.revanced.manager.flutter.utils.aligning.ZipAligner
import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver import app.revanced.manager.flutter.utils.packageInstaller.InstallerReceiver
import app.revanced.manager.flutter.utils.packageInstaller.UninstallerReceiver 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.PatchBundleLoader
import app.revanced.patcher.PatchSet import app.revanced.patcher.PatchSet
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherConfig
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.json.JSONArray import org.json.JSONArray
@ -50,7 +47,10 @@ class MainActivity : FlutterActivity() {
val installerChannel = "app.revanced.manager.flutter/installer" val installerChannel = "app.revanced.manager.flutter/installer"
val openBrowserChannel = "app.revanced.manager.flutter/browser" 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") { if (call.method == "openBrowser") {
val searchQuery = call.argument<String>("query") val searchQuery = call.argument<String>("query")
openBrowser(searchQuery) openBrowser(searchQuery)
@ -69,40 +69,34 @@ class MainActivity : FlutterActivity() {
mainChannel.setMethodCallHandler { call, result -> mainChannel.setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"runPatcher" -> { "runPatcher" -> {
val originalFilePath = call.argument<String>("originalFilePath") val inFilePath = call.argument<String>("inFilePath")
val inputFilePath = call.argument<String>("inputFilePath")
val patchedFilePath = call.argument<String>("patchedFilePath")
val outFilePath = call.argument<String>("outFilePath") val outFilePath = call.argument<String>("outFilePath")
val integrationsPath = call.argument<String>("integrationsPath") val integrationsPath = call.argument<String>("integrationsPath")
val selectedPatches = call.argument<List<String>>("selectedPatches") val selectedPatches = call.argument<List<String>>("selectedPatches")
val options = call.argument<Map<String, Map<String, Any>>>("options") val options = call.argument<Map<String, Map<String, Any>>>("options")
val cacheDirPath = call.argument<String>("cacheDirPath") val tmpDirPath = call.argument<String>("tmpDirPath")
val keyStoreFilePath = call.argument<String>("keyStoreFilePath") val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
val keystorePassword = call.argument<String>("keystorePassword") val keystorePassword = call.argument<String>("keystorePassword")
if ( if (
originalFilePath != null && inFilePath != null &&
inputFilePath != null &&
patchedFilePath != null &&
outFilePath != null && outFilePath != null &&
integrationsPath != null && integrationsPath != null &&
selectedPatches != null && selectedPatches != null &&
options != null && options != null &&
cacheDirPath != null && tmpDirPath != null &&
keyStoreFilePath != null && keyStoreFilePath != null &&
keystorePassword != null keystorePassword != null
) { ) {
cancel = false cancel = false
runPatcher( runPatcher(
result, result,
originalFilePath, inFilePath,
inputFilePath,
patchedFilePath,
outFilePath, outFilePath,
integrationsPath, integrationsPath,
selectedPatches, selectedPatches,
options, options,
cacheDirPath, tmpDirPath,
keyStoreFilePath, keyStoreFilePath,
keystorePassword keystorePassword
) )
@ -215,27 +209,22 @@ class MainActivity : FlutterActivity() {
} }
} }
@OptIn(InternalCoroutinesApi::class)
private fun runPatcher( private fun runPatcher(
result: MethodChannel.Result, result: MethodChannel.Result,
originalFilePath: String, inFilePath: String,
inputFilePath: String,
patchedFilePath: String,
outFilePath: String, outFilePath: String,
integrationsPath: String, integrationsPath: String,
selectedPatches: List<String>, selectedPatches: List<String>,
options: Map<String, Map<String, Any>>, options: Map<String, Map<String, Any>>,
cacheDirPath: String, tmpDirPath: String,
keyStoreFilePath: String, keyStoreFilePath: String,
keystorePassword: String keystorePassword: String
) { ) {
val originalFile = File(originalFilePath) val inFile = File(inFilePath)
val inputFile = File(inputFilePath)
val patchedFile = File(patchedFilePath)
val outFile = File(outFilePath) val outFile = File(outFilePath)
val integrations = File(integrationsPath) val integrations = File(integrationsPath)
val keyStoreFile = File(keyStoreFilePath) val keyStoreFile = File(keyStoreFilePath)
val cacheDir = File(cacheDirPath) val tmpDir = File(tmpDirPath)
Thread { Thread {
fun updateProgress(progress: Double, header: String, log: String) { fun updateProgress(progress: Double, header: String, log: String) {
@ -253,6 +242,16 @@ class MainActivity : FlutterActivity() {
fun postStop() = handler.post { stopResult!!.success(null) } fun postStop() = handler.post { stopResult!!.success(null) }
fun cancel(block: () -> Unit = {}): Boolean {
if (cancel) {
block()
postStop()
}
return cancel
}
// Setup logger // Setup logger
Logger.getLogger("").apply { Logger.getLogger("").apply {
handlers.forEach { handlers.forEach {
@ -273,37 +272,19 @@ class MainActivity : FlutterActivity() {
} }
try { try {
updateProgress(0.0, "", "Copying APK") updateProgress(0.0, "Reading APK...", "Reading APK")
if (cancel) {
postStop()
return@Thread
}
originalFile.copyTo(inputFile, true)
if (cancel) {
postStop()
return@Thread
}
updateProgress(0.05, "Reading APK...", "Reading APK")
val patcher = Patcher( val patcher = Patcher(
PatcherOptions( PatcherConfig(
inputFile, inFile,
cacheDir, tmpDir,
Aapt.binary(applicationContext).absolutePath, Aapt.binary(applicationContext).absolutePath,
cacheDir.path, tmpDir.path,
true // TODO: Add option to disable this true // TODO: Add option to disable this
) )
) )
if (cancel) { if (cancel(patcher::close)) return@Thread
postStop()
return@Thread
}
updateProgress(0.1, "Loading patches...", "Loading patches") updateProgress(0.1, "Loading patches...", "Loading patches")
val patches = patches.filter { patch -> val patches = patches.filter { patch ->
@ -319,32 +300,25 @@ class MainActivity : FlutterActivity() {
options[patch.name]?.forEach { (key, value) -> options[patch.name]?.forEach { (key, value) ->
patch.options[key] = value patch.options[key] = value
} }
} }.toSet()
if (cancel) {
postStop()
return@Thread
}
if (cancel(patcher::close)) return@Thread
updateProgress(0.15, "Executing...", "") updateProgress(0.15, "Executing...", "")
// Update the progress bar every time a patch is executed from 0.15 to 0.7 val patcherResult = patcher.use {
val totalPatchesCount = patches.size patcher.apply {
val progressStep = 0.55 / totalPatchesCount acceptIntegrations(setOf(integrations))
var progress = 0.15 acceptPatches(patches)
}
patcher.apply {
acceptIntegrations(listOf(integrations))
acceptPatches(patches)
runBlocking { runBlocking {
apply(false).collect(FlowCollector { patchResult: PatchResult -> // Update the progress bar every time a patch is executed from 0.15 to 0.7
if (cancel) { val totalPatchesCount = patches.size
handler.post { stopResult!!.success(null) } val progressStep = 0.55 / totalPatchesCount
this.cancel() var progress = 0.15
this@apply.close()
return@FlowCollector patcher.apply(false).collect(FlowCollector { patchResult: PatchResult ->
} if (cancel(patcher::close)) return@FlowCollector
val msg = patchResult.exception?.let { val msg = patchResult.exception?.let {
val writer = StringWriter() val writer = StringWriter()
@ -358,50 +332,30 @@ class MainActivity : FlutterActivity() {
progress += progressStep progress += progressStep
}) })
} }
if (cancel(patcher::close)) return@Thread
updateProgress(0.75, "Building...", "")
patcher.get()
} }
if (cancel) { inFile.copyTo(outFile)
postStop()
patcher.close()
return@Thread
}
updateProgress(0.75, "Building...", "") if (cancel(patcher::close)) return@Thread
val res = patcher.get() patcherResult.applyTo(outFile)
patcher.close()
ZipFile(patchedFile).use { file -> if (cancel(patcher::close)) return@Thread
res.dexFiles.forEach { updateProgress(0.8, "Signing...", "")
if (cancel) {
postStop() outFile.sign(
return@Thread ApkUtils.SigningOptions(
} keyStoreFile,
file.addEntryCompressData( keystorePassword,
ZipEntry.createWithName(it.name), "alias",
it.stream.readBytes() keystorePassword
)
}
res.resourceFile?.let {
file.copyEntriesFromFileAligned(
ZipFile(it),
ZipAligner::getEntryAlignment
)
}
file.copyEntriesFromFileAligned(
ZipFile(inputFile),
ZipAligner::getEntryAlignment
) )
} )
if (cancel) {
postStop()
return@Thread
}
updateProgress(0.8, "Signing...", "Signing APK")
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
updateProgress(.85, "Patched", "Patched APK") updateProgress(.85, "Patched", "Patched APK")
} catch (ex: Throwable) { } catch (ex: Throwable) {
@ -421,7 +375,8 @@ class MainActivity : FlutterActivity() {
private fun installApk(apkPath: String) { private fun installApk(apkPath: String) {
val packageInstaller: PackageInstaller = applicationContext.packageManager.packageInstaller 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 sessionId: Int = packageInstaller.createSession(sessionParams)
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId) val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
session.use { activeSession -> session.use { activeSession ->
@ -436,7 +391,12 @@ class MainActivity : FlutterActivity() {
val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply { val receiverIntent = Intent(applicationContext, InstallerReceiver::class.java).apply {
action = "APP_INSTALL_ACTION" 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.commit(receiverPendingIntent.intentSender)
session.close() session.close()
} }
@ -446,7 +406,8 @@ class MainActivity : FlutterActivity() {
val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply { val receiverIntent = Intent(applicationContext, UninstallerReceiver::class.java).apply {
action = "APP_UNINSTALL_ACTION" 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) packageInstaller.uninstall(packageName, receiverPendingIntent.intentSender)
} }

View File

@ -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
}

View File

@ -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<X509Certificate, PrivateKey> {
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()
}
}

View File

@ -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())

View File

@ -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<ZipEntry> = 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<ZipEntry> {
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()
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -3,7 +3,12 @@ allprojects {
google() google()
mavenCentral() mavenCentral()
maven { 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() mavenLocal()
} }

View File

@ -28,3 +28,14 @@ Learn how to build ReVanced Manager from source.
```sh ```sh
flutter build apk 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
> ```

View File

@ -172,25 +172,22 @@ class PatcherAPI {
_dataDir.createSync(); _dataDir.createSync();
_tmpDir.createSync(); _tmpDir.createSync();
final Directory workDir = _tmpDir.createTempSync('tmp-'); 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'); outFile = File('${workDir.path}/out.apk');
final Directory cacheDir = Directory('${workDir.path}/cache');
cacheDir.createSync(); final Directory tmpDir =
final String originalFilePath = apkFilePath; Directory('${workDir.path}/revanced-temporary-files');
try { try {
await patcherChannel.invokeMethod( await patcherChannel.invokeMethod(
'runPatcher', 'runPatcher',
{ {
'originalFilePath': originalFilePath, 'inFilePath': apkFilePath,
'inputFilePath': inputFile.path,
'patchedFilePath': patchedFile.path,
'outFilePath': outFile!.path, 'outFilePath': outFile!.path,
'integrationsPath': integrationsFile.path, 'integrationsPath': integrationsFile.path,
'selectedPatches': selectedPatches.map((p) => p.name).toList(), 'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'options': options, 'options': options,
'cacheDirPath': cacheDir.path, 'tmpDirPath': tmpDir.path,
'keyStoreFilePath': _keyStoreFile.path, 'keyStoreFilePath': _keyStoreFile.path,
'keystorePassword': _managerAPI.getKeystorePassword(), 'keystorePassword': _managerAPI.getKeystorePassword(),
}, },
@ -462,7 +459,6 @@ enum InstallStatus {
mountNoRoot(1), mountNoRoot(1),
mountVersionMismatch(1.1), mountVersionMismatch(1.1),
mountMissingInstallation(1.2), mountMissingInstallation(1.2),
statusFailureBlocked(2), statusFailureBlocked(2),
installFailedVerificationFailure(3.1), installFailedVerificationFailure(3.1),
statusFailureInvalid(4), statusFailureInvalid(4),
@ -473,6 +469,7 @@ enum InstallStatus {
statusFailureTimeout(8); statusFailureTimeout(8);
const InstallStatus(this.statusCode); const InstallStatus(this.statusCode);
final double statusCode; final double statusCode;
static String byCode(num code) { static String byCode(num code) {

View File

@ -2,13 +2,10 @@ import 'dart:ui';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:dynamic_themes/dynamic_themes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:google_fonts/google_fonts.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/app/app.router.dart';
import 'package:revanced_manager/gen/strings.g.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:revanced_manager/theme.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';