Repackager.kt (3321B) - raw
1 package me.rhunk.snapenhance.manager.patch 2 3 import android.content.Context 4 import com.android.tools.build.apkzlib.zip.AlignmentRules 5 import com.android.tools.build.apkzlib.zip.ZFile 6 import com.android.tools.build.apkzlib.zip.ZFileOptions 7 import com.wind.meditor.core.ManifestEditor 8 import com.wind.meditor.property.AttributeItem 9 import com.wind.meditor.property.ModificationProperty 10 import me.rhunk.snapenhance.manager.BuildConfig 11 import me.rhunk.snapenhance.manager.patch.util.ApkSignatureHelper.provideSigningExtension 12 import me.rhunk.snapenhance.manager.patch.util.obfuscateDexFile 13 import java.io.ByteArrayInputStream 14 import java.io.ByteArrayOutputStream 15 import java.io.File 16 17 class Repackager( 18 private val context: Context, 19 private val cacheFolder: File, 20 private val packageName: String, 21 ) { 22 private fun patchManifest(data: ByteArray): ByteArray { 23 val property = ModificationProperty() 24 25 property.addManifestAttribute(AttributeItem("package", packageName).apply { 26 type = 3 27 namespace = null 28 }) 29 30 return ByteArrayOutputStream().apply { 31 ManifestEditor(ByteArrayInputStream(data), this, property).processManifest() 32 flush() 33 close() 34 }.toByteArray() 35 } 36 37 fun patch(apkFile: File): File { 38 val outputFile = File(cacheFolder, "patched-${apkFile.name}") 39 runCatching { 40 patch(apkFile, outputFile) 41 }.onFailure { 42 outputFile.delete() 43 throw it 44 } 45 return outputFile 46 } 47 48 fun patch(apkFile: File, outputFile: File) { 49 val dstZFile = ZFile.openReadWrite(outputFile, ZFileOptions().setAlignmentRule( 50 AlignmentRules.compose(AlignmentRules.constantForSuffix(".so", 4096)) 51 )) 52 provideSigningExtension(context.assets.open("lspatch/keystore.jks")).register(dstZFile) 53 val srcZFile = ZFile.openReadOnly(apkFile) 54 val dexFiles = mutableListOf<File>() 55 56 for (entry in srcZFile.entries()) { 57 val name = entry.centralDirectoryHeader.name 58 if (name.startsWith("AndroidManifest.xml")) { 59 dstZFile.add(name, ByteArrayInputStream( 60 patchManifest(entry.read()) 61 ), false) 62 continue 63 } 64 if (name.startsWith("classes") && name.endsWith(".dex")) { 65 println("obfuscating $name") 66 val inputStream = entry.open() ?: continue 67 val obfuscatedDexFile = inputStream.obfuscateDexFile(cacheFolder, { dexFile -> 68 dexFile.classes.firstOrNull { it.type == "Lme/rhunk/snapenhance/common/Constants;" } != null 69 }, mapOf( 70 BuildConfig.APPLICATION_ID to packageName 71 ))?.also { dexFiles.add(it) } 72 73 if (obfuscatedDexFile == null) { 74 inputStream.close() 75 dstZFile.add(name, entry.open(), false) 76 continue 77 } 78 79 dstZFile.add(name, obfuscatedDexFile.inputStream(), false) 80 continue 81 } 82 dstZFile.add(name, entry.open(), false) 83 } 84 dstZFile.realign() 85 dstZFile.close() 86 srcZFile.close() 87 dexFiles.forEach { it.delete() } 88 } 89 }