mirror of
https://github.com/revanced/revanced-cli.git
synced 2024-12-24 02:55:48 +01:00
fix: invalid header when writing a ZipFile
(#169)
This commit is contained in:
parent
c677eb9792
commit
6e703eb8e8
@ -1,12 +1,37 @@
|
||||
package app.revanced.cli.aligning
|
||||
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import app.revanced.utils.signing.align.ZipAligner
|
||||
import app.revanced.utils.signing.align.zip.ZipFile
|
||||
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||
import java.io.File
|
||||
|
||||
object Aligning {
|
||||
fun align(inputFile: File, outputFile: File) {
|
||||
fun align(result: PatcherResult, inputFile: File, outputFile: File) {
|
||||
logger.info("Aligning ${inputFile.name} to ${outputFile.name}")
|
||||
ZipAligner.align(inputFile, outputFile)
|
||||
|
||||
if (outputFile.exists()) outputFile.delete()
|
||||
|
||||
ZipFile(outputFile).use { file ->
|
||||
result.dexFiles.forEach {
|
||||
file.addEntryCompressData(
|
||||
ZipEntry.createWithName(it.name),
|
||||
it.stream.readBytes()
|
||||
)
|
||||
}
|
||||
|
||||
result.resourceFile?.let {
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(it),
|
||||
ZipAligner::getEntryAlignment
|
||||
)
|
||||
}
|
||||
|
||||
file.copyEntriesFromFileAligned(
|
||||
ZipFile(inputFile),
|
||||
ZipAligner::getEntryAlignment
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,16 +151,14 @@ internal object MainCommand : Runnable {
|
||||
Adb(outputFile, patcher.context.packageMetadata.packageName, args.deploy!!, !pArgs.mount)
|
||||
}
|
||||
|
||||
val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")
|
||||
|
||||
// start the patcher
|
||||
Patcher.start(patcher, patchedFile, allPatches)
|
||||
val result = Patcher.start(patcher, allPatches)
|
||||
|
||||
val cacheDirectory = File(pArgs.cacheDirectory)
|
||||
|
||||
// align the file
|
||||
val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
|
||||
Aligning.align(patchedFile, alignedFile)
|
||||
Aligning.align(result, args.inputFile, alignedFile)
|
||||
|
||||
// sign the file
|
||||
val finalFile = if (!pArgs.mount) {
|
||||
|
@ -1,24 +1,17 @@
|
||||
package app.revanced.cli.patcher
|
||||
|
||||
import app.revanced.cli.command.MainCommand.args
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.utils.filesystem.ZipFileSystemUtils
|
||||
import app.revanced.utils.patcher.addPatchesFiltered
|
||||
import app.revanced.utils.patcher.applyPatchesVerbose
|
||||
import app.revanced.utils.patcher.mergeFiles
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
internal object Patcher {
|
||||
internal fun start(
|
||||
patcher: app.revanced.patcher.Patcher,
|
||||
output: File,
|
||||
allPatches: List<Class<out Patch<Context>>>
|
||||
) {
|
||||
val inputFile = args.inputFile
|
||||
|
||||
): PatcherResult {
|
||||
// merge files like necessary integrations
|
||||
patcher.mergeFiles()
|
||||
// add patches, but filter incompatible or excluded patches
|
||||
@ -26,28 +19,6 @@ internal object Patcher {
|
||||
// apply patches
|
||||
patcher.applyPatchesVerbose()
|
||||
|
||||
// write output file
|
||||
if (output.exists()) Files.delete(output.toPath())
|
||||
inputFile.copyTo(output)
|
||||
|
||||
val result = patcher.save()
|
||||
ZipFileSystemUtils(output).use { outputFileSystem ->
|
||||
// replace all dex files
|
||||
result.dexFiles.forEach {
|
||||
logger.info("Writing dex file ${it.name}")
|
||||
outputFileSystem.write(it.name, it.stream.readAllBytes())
|
||||
}
|
||||
|
||||
result.resourceFile?.let {
|
||||
logger.info("Writing resources...")
|
||||
|
||||
ZipFileSystemUtils(it).use { resourceFileSystem ->
|
||||
val resourceFiles = resourceFileSystem.getFile(File.separator)
|
||||
outputFileSystem.writePathRecursively(resourceFiles)
|
||||
}
|
||||
}
|
||||
|
||||
result.doNotCompress?.let { outputFileSystem.uncompress(*it.toTypedArray()) }
|
||||
}
|
||||
return patcher.save()
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,11 @@
|
||||
package app.revanced.utils.signing.align
|
||||
|
||||
import app.revanced.utils.signing.align.zip.ZipFile
|
||||
import java.io.File
|
||||
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||
|
||||
internal object ZipAligner {
|
||||
private const val DEFAULT_ALIGNMENT = 4
|
||||
private const val LIBRARY_ALIGNMENT = 4096
|
||||
|
||||
fun align(input: File, output: File) {
|
||||
val inputZip = ZipFile(input)
|
||||
val outputZip = ZipFile(output)
|
||||
|
||||
for (entry in inputZip.entries) {
|
||||
val data = inputZip.getDataForEntry(entry)
|
||||
|
||||
if (entry.compression == 0.toUShort()) {
|
||||
val alignment = if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||
|
||||
outputZip.addEntryAligned(entry, data, alignment)
|
||||
} else {
|
||||
outputZip.addEntry(entry, data)
|
||||
}
|
||||
}
|
||||
|
||||
outputZip.finish()
|
||||
}
|
||||
fun getEntryAlignment(entry: ZipEntry): Int? =
|
||||
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||
}
|
||||
|
@ -2,15 +2,21 @@ package app.revanced.utils.signing.align.zip
|
||||
|
||||
import app.revanced.utils.signing.align.zip.structures.ZipEndRecord
|
||||
import app.revanced.utils.signing.align.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(val file: File) {
|
||||
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
|
||||
@ -53,23 +59,24 @@ class ZipFile(val file: File) {
|
||||
|
||||
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
|
||||
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 writeCDE() {
|
||||
val CDEStart = filePointer.channel.position().toUInt()
|
||||
private fun writeCD() {
|
||||
val CDStart = filePointer.channel.position().toUInt()
|
||||
|
||||
entries.forEach {
|
||||
filePointer.channel.write(it.toCDE())
|
||||
@ -82,15 +89,17 @@ class ZipFile(val file: File) {
|
||||
0u,
|
||||
entriesCount,
|
||||
entriesCount,
|
||||
filePointer.channel.position().toUInt() - CDEStart,
|
||||
CDEStart,
|
||||
filePointer.channel.position().toUInt() - CDStart,
|
||||
CDStart,
|
||||
""
|
||||
)
|
||||
|
||||
filePointer.channel.write(endRecord.toECD())
|
||||
}
|
||||
|
||||
fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||
CDNeedsRewrite = true
|
||||
|
||||
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
||||
|
||||
filePointer.channel.write(entry.toLFH())
|
||||
@ -99,17 +108,45 @@ class ZipFile(val file: File) {
|
||||
entries.add(entry)
|
||||
}
|
||||
|
||||
fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) {
|
||||
//calculate where data would end up
|
||||
val dataOffset = filePointer.filePointer + entry.LFHSize
|
||||
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
||||
val compressor = Deflater(compressionLevel, true)
|
||||
compressor.setInput(data)
|
||||
compressor.finish()
|
||||
|
||||
val mod = dataOffset % alignment
|
||||
val uncompressedSize = data.size
|
||||
val compressedData =
|
||||
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
|
||||
|
||||
//wrong alignment
|
||||
if (mod != 0L) {
|
||||
//add padding at end of extra field
|
||||
entry.localExtraField =
|
||||
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
||||
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)
|
||||
@ -123,8 +160,17 @@ class ZipFile(val file: File) {
|
||||
)
|
||||
}
|
||||
|
||||
fun finish() {
|
||||
writeCDE()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -9,12 +9,12 @@ data class ZipEntry(
|
||||
val version: UShort,
|
||||
val versionNeeded: UShort,
|
||||
val flags: UShort,
|
||||
val compression: UShort,
|
||||
var compression: UShort,
|
||||
val modificationTime: UShort,
|
||||
val modificationDate: UShort,
|
||||
val crc32: UInt,
|
||||
val compressedSize: UInt,
|
||||
val uncompressedSize: UInt,
|
||||
var crc32: UInt,
|
||||
var compressedSize: UInt,
|
||||
var uncompressedSize: UInt,
|
||||
val diskNumber: UShort,
|
||||
val internalAttributes: UShort,
|
||||
val externalAttributes: UInt,
|
||||
@ -22,7 +22,7 @@ data class ZipEntry(
|
||||
val fileName: String,
|
||||
val extraField: ByteArray,
|
||||
val fileComment: String,
|
||||
var localExtraField: ByteArray = ByteArray(0), //seperate for alignment
|
||||
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
||||
) {
|
||||
val LFHSize: Int
|
||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||
@ -37,6 +37,27 @@ data class ZipEntry(
|
||||
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()
|
||||
|
||||
@ -55,7 +76,7 @@ data class ZipEntry(
|
||||
val fileNameLength = input.readUShortLE()
|
||||
var fileName = ""
|
||||
val extraFieldLength = input.readUShortLE()
|
||||
var extraField = ByteArray(extraFieldLength.toInt())
|
||||
val extraField = ByteArray(extraFieldLength.toInt())
|
||||
val fileCommentLength = input.readUShortLE()
|
||||
var fileComment = ""
|
||||
val diskNumber = input.readUShortLE()
|
||||
@ -63,7 +84,8 @@ data class ZipEntry(
|
||||
val externalAttributes = input.readUIntLE()
|
||||
val localHeaderOffset = input.readUIntLE()
|
||||
|
||||
val variableFieldsLength = fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
||||
val variableFieldsLength =
|
||||
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
||||
|
||||
if (variableFieldsLength > 0) {
|
||||
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
||||
@ -77,7 +99,8 @@ data class ZipEntry(
|
||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
flags = (flags and 0b1000u.inv().toUShort()) //disable data descriptor flag as they are not used
|
||||
flags = (flags and 0b1000u.inv()
|
||||
.toUShort()) //disable data descriptor flag as they are not used
|
||||
|
||||
return ZipEntry(
|
||||
version,
|
||||
@ -134,8 +157,9 @@ data class ZipEntry(
|
||||
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) }
|
||||
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)
|
||||
@ -162,5 +186,4 @@ data class ZipEntry(
|
||||
buffer.flip()
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user