Skip to content

Commit bfe43d0

Browse files
authored
feat: Support patching APKM bundles (#40)
1 parent 4eb90c4 commit bfe43d0

3 files changed

Lines changed: 61 additions & 4 deletions

File tree

build.gradle.kts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,33 @@ repositories {
3232
maven { url = uri("https://jitpack.io") }
3333
}
3434

35+
val apkEditorLib by configurations.creating
36+
37+
val strippedApkEditorLib by tasks.registering(org.gradle.jvm.tasks.Jar::class) {
38+
archiveFileName.set("APKEditor-cli.jar")
39+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
40+
doFirst {
41+
from(apkEditorLib.resolve().map { zipTree(it) })
42+
}
43+
exclude(
44+
"org/xmlpull/**",
45+
"antlr/**",
46+
"org/antlr/**",
47+
"com/beust/jcommander/**",
48+
"javax/annotation/**",
49+
"smali.properties",
50+
"baksmali.properties"
51+
)
52+
}
53+
3554
dependencies {
3655
implementation(libs.morphe.patcher)
3756
implementation(libs.morphe.library)
3857
implementation(libs.kotlinx.coroutines.core)
3958
implementation(libs.kotlinx.serialization.json)
4059
implementation(libs.picocli)
60+
apkEditorLib(files("$rootDir/libs/APKEditor-1.4.7.jar"))
61+
implementation(files(strippedApkEditorLib))
4162

4263
testImplementation(libs.kotlin.test)
4364
}
@@ -65,7 +86,11 @@ tasks {
6586
}
6687

6788
shadowJar {
68-
exclude("/prebuilt/linux/aapt", "/prebuilt/windows/aapt.exe", "/prebuilt/*/aapt_*")
89+
exclude(
90+
"/prebuilt/linux/aapt",
91+
"/prebuilt/windows/aapt.exe",
92+
"/prebuilt/*/aapt_*",
93+
)
6994
minimize {
7095
exclude(dependency("org.bouncycastle:.*"))
7196
exclude(dependency("app.morphe:morphe-patcher"))

libs/APKEditor-1.4.7.jar

7.32 MB
Binary file not shown.

src/main/kotlin/app/morphe/cli/command/PatchCommand.kt

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import app.morphe.patcher.Patcher
1414
import app.morphe.patcher.PatcherConfig
1515
import app.morphe.patcher.patch.Patch
1616
import app.morphe.patcher.patch.loadPatchesFromJar
17+
import com.reandroid.apkeditor.merge.Merger
18+
import com.reandroid.apkeditor.merge.MergerOptions
1719
import kotlinx.coroutines.runBlocking
1820
import kotlinx.serialization.ExperimentalSerializationApi
1921
import kotlinx.serialization.json.Json
@@ -260,7 +262,7 @@ internal object PatchCommand : Runnable {
260262

261263
val outputFilePath =
262264
outputFilePath ?: File("").absoluteFile.resolve(
263-
"${apk.nameWithoutExtension}-patched.${apk.extension}",
265+
"${apk.nameWithoutExtension}-patched.apk",
264266
)
265267

266268
val temporaryFilesPath =
@@ -312,12 +314,34 @@ internal object PatchCommand : Runnable {
312314

313315
val patcherTemporaryFilesPath = temporaryFilesPath.resolve("patcher")
314316

317+
// Checking if the file is in apkm format (like reddit)
318+
var mergedApkToCleanup: File? = null
319+
val inputApk = if (apk.extension.equals("apkm", ignoreCase = true)) {
320+
logger.info("Merging APKM bundle")
321+
322+
// Save merged APK to output directory (will be cleaned up after patching)
323+
val outputApk = outputFilePath.parentFile.resolve("${apk.nameWithoutExtension}-merged.apk")
324+
325+
// Use APKEditor's Merger directly (handles extraction and merging)
326+
val mergerOptions = MergerOptions().apply {
327+
inputFile = apk // Original APKM file
328+
outputFile = outputApk
329+
cleanMeta = true
330+
}
331+
Merger(mergerOptions).run()
332+
333+
mergedApkToCleanup = outputApk
334+
outputApk
335+
} else {
336+
apk
337+
}
338+
315339
val patchingResult = PatchingResult()
316340

317341
try {
318342
val (packageName, patcherResult) = Patcher(
319343
PatcherConfig(
320-
apk,
344+
inputApk,
321345
patcherTemporaryFilesPath,
322346
aaptBinaryPath?.path,
323347
patcherTemporaryFilesPath.absolutePath,
@@ -377,7 +401,7 @@ internal object PatchCommand : Runnable {
377401

378402
// region Save.
379403

380-
apk.copyTo(temporaryFilesPath.resolve(apk.name), overwrite = true).apply {
404+
inputApk.copyTo(temporaryFilesPath.resolve(inputApk.name), overwrite = true).apply {
381405
patchingResult.addStepResult(
382406
PatchingStep.REBUILDING,
383407
{
@@ -406,6 +430,7 @@ internal object PatchCommand : Runnable {
406430
patchedApkFile.copyTo(outputFilePath, overwrite = true)
407431
}
408432
}
433+
409434
logger.info("Saved to $outputFilePath")
410435

411436
// endregion
@@ -448,6 +473,13 @@ internal object PatchCommand : Runnable {
448473
logger.info("Purging temporary files")
449474
purge(temporaryFilesPath)
450475
}
476+
477+
// Clean up merged APK if we created one from APKM
478+
mergedApkToCleanup?.let {
479+
if (!it.delete()) {
480+
logger.warning("Could not clean up merged APK: ${it.path}")
481+
}
482+
}
451483
}
452484

453485
/**

0 commit comments

Comments
 (0)