From 37239db5f17c235d2076cfcb8de0886b73f2cc64 Mon Sep 17 00:00:00 2001 From: Prateek <129204458+prateek-who@users.noreply.github.com> Date: Sun, 29 Mar 2026 02:32:03 +0530 Subject: [PATCH 1/5] default key store matches manager --- src/main/kotlin/app/morphe/cli/command/PatchCommand.kt | 4 ++-- src/main/kotlin/app/morphe/engine/PatchEngine.kt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt index 31efd72..7e4ace2 100644 --- a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt @@ -196,13 +196,13 @@ internal object PatchCommand : Callable { description = ["Alias of the private key and certificate pair keystore entry."], showDefaultValue = ALWAYS, ) - private var keyStoreEntryAlias = "Morphe Key" + private var keyStoreEntryAlias = "Morphe" // Default now matches Manager @CommandLine.Option( names = ["--keystore-entry-password"], description = ["Password of the keystore entry."], ) - private var keyStoreEntryPassword = "" // Empty password by default + private var keyStoreEntryPassword = "Morphe" // Default now matches Manager @CommandLine.Option( names = ["--signer"], diff --git a/src/main/kotlin/app/morphe/engine/PatchEngine.kt b/src/main/kotlin/app/morphe/engine/PatchEngine.kt index 72f6d45..21972e3 100644 --- a/src/main/kotlin/app/morphe/engine/PatchEngine.kt +++ b/src/main/kotlin/app/morphe/engine/PatchEngine.kt @@ -26,7 +26,7 @@ import java.io.StringWriter import java.nio.file.Files import java.util.logging.Logger -/** +/* * Single patching pipeline shared by CLI and GUI. (Eventually. Right now we are still having 2 pipelines) */ object PatchEngine { @@ -231,8 +231,8 @@ object PatchEngine { val keystoreDetails = config.keystoreDetails ?: ApkUtils.KeyStoreDetails( File(tempDir, "morphe.keystore"), null, - "Morphe Key", - "", + "Morphe", + "Morphe", ) ApkUtils.signApk( rebuiltApk, From 2f10a981f6369da0322c3acb25daacfc248d9c9e Mon Sep 17 00:00:00 2001 From: Prateek <129204458+prateek-who@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:40:02 +0530 Subject: [PATCH 2/5] added legacy keystore creds support --- .../app/morphe/cli/command/PatchCommand.kt | 43 ++++++++++++++----- .../kotlin/app/morphe/engine/PatchEngine.kt | 35 ++++++++++++--- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt index 7e4ace2..910dd36 100644 --- a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt @@ -662,17 +662,38 @@ internal object PatchCommand : Callable { patchingResult.addStepResult( PatchingStep.SIGNING, { - ApkUtils.signApk( - patchedApkFile, - outputFilePath, - signer, - ApkUtils.KeyStoreDetails( - keystoreFilePath, - keyStorePassword, - keyStoreEntryAlias, - keyStoreEntryPassword, - ), - ) + try { + ApkUtils.signApk( + patchedApkFile, + outputFilePath, + signer, + ApkUtils.KeyStoreDetails( + keystoreFilePath, + keyStorePassword, + keyStoreEntryAlias, + keyStoreEntryPassword, + ), + ) + } catch (e: Exception){ + // We retry with legacy keystore defaults here. Need to move to new defaults eventually! + if (keyStoreEntryAlias == "Morphe" && keyStoreEntryPassword == "Morphe" && keystoreFilePath.exists()){ + logger.info("Retrying with legacy keystore credentials...") + + ApkUtils.signApk( + patchedApkFile, + outputFilePath, + signer, + ApkUtils.KeyStoreDetails( + keystoreFilePath, + keyStorePassword, + "Morphe Key", + "", + ) + ) + } else { + throw e + } + } } ) } else { diff --git a/src/main/kotlin/app/morphe/engine/PatchEngine.kt b/src/main/kotlin/app/morphe/engine/PatchEngine.kt index 21972e3..e5aacf0 100644 --- a/src/main/kotlin/app/morphe/engine/PatchEngine.kt +++ b/src/main/kotlin/app/morphe/engine/PatchEngine.kt @@ -234,12 +234,35 @@ object PatchEngine { "Morphe", "Morphe", ) - ApkUtils.signApk( - rebuiltApk, - tempOutput, - config.signerName, - keystoreDetails, - ) + try { + ApkUtils.signApk( + rebuiltApk, + tempOutput, + config.signerName, + keystoreDetails, + ) + } catch (e: Exception){ + // We retry with legacy keystore defaults here. Need to move to new defaults eventually! + if (config.keystoreDetails == null && keystoreDetails.keyStore.exists()){ + onProgress("Retrying with legacy keystore credentials...") + + val legacyKeystoreDetails = ApkUtils.KeyStoreDetails( + keystoreDetails.keyStore, + null, + "Morphe Key", + "", + ) + + ApkUtils.signApk( + rebuiltApk, + tempOutput, + config.signerName, + legacyKeystoreDetails, + ) + } else { + throw e + } + } stepResults.add(StepResult(PatchStep.SIGNING, true)) } catch (e: Exception) { stepResults.add(StepResult(PatchStep.SIGNING, false, e.toString())) From 4a4c3cb2c0ff8ee27e3c01bdb99d556271e1d401 Mon Sep 17 00:00:00 2001 From: Prateek <129204458+prateek-who@users.noreply.github.com> Date: Mon, 30 Mar 2026 17:47:59 +0530 Subject: [PATCH 3/5] keystore constants are not private vals --- .../kotlin/app/morphe/cli/command/PatchCommand.kt | 15 ++++++++++----- src/main/kotlin/app/morphe/engine/PatchEngine.kt | 14 ++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt index 910dd36..6c344c9 100644 --- a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt @@ -47,6 +47,11 @@ import java.io.StringWriter import java.util.concurrent.Callable import java.util.logging.Logger +private const val DEFAULT_KEYSTORE_ALIAS = "Morphe" +private const val DEFAULT_KEYSTORE_PASSWORD = "Morphe" +private const val LEGACY_KEYSTORE_ALIAS = "Morphe Key" +private const val LEGACY_KEYSTORE_PASSWORD = "" + @OptIn(ExperimentalSerializationApi::class) @VisibleForTesting @CommandLine.Command( @@ -196,13 +201,13 @@ internal object PatchCommand : Callable { description = ["Alias of the private key and certificate pair keystore entry."], showDefaultValue = ALWAYS, ) - private var keyStoreEntryAlias = "Morphe" // Default now matches Manager + private var keyStoreEntryAlias = DEFAULT_KEYSTORE_ALIAS // Default now matches Manager @CommandLine.Option( names = ["--keystore-entry-password"], description = ["Password of the keystore entry."], ) - private var keyStoreEntryPassword = "Morphe" // Default now matches Manager + private var keyStoreEntryPassword = DEFAULT_KEYSTORE_PASSWORD // Default now matches Manager @CommandLine.Option( names = ["--signer"], @@ -676,7 +681,7 @@ internal object PatchCommand : Callable { ) } catch (e: Exception){ // We retry with legacy keystore defaults here. Need to move to new defaults eventually! - if (keyStoreEntryAlias == "Morphe" && keyStoreEntryPassword == "Morphe" && keystoreFilePath.exists()){ + if (keyStoreEntryAlias == DEFAULT_KEYSTORE_ALIAS && keyStoreEntryPassword == DEFAULT_KEYSTORE_PASSWORD && keystoreFilePath.exists()){ logger.info("Retrying with legacy keystore credentials...") ApkUtils.signApk( @@ -686,8 +691,8 @@ internal object PatchCommand : Callable { ApkUtils.KeyStoreDetails( keystoreFilePath, keyStorePassword, - "Morphe Key", - "", + LEGACY_KEYSTORE_ALIAS, + LEGACY_KEYSTORE_PASSWORD, ) ) } else { diff --git a/src/main/kotlin/app/morphe/engine/PatchEngine.kt b/src/main/kotlin/app/morphe/engine/PatchEngine.kt index e5aacf0..d0870de 100644 --- a/src/main/kotlin/app/morphe/engine/PatchEngine.kt +++ b/src/main/kotlin/app/morphe/engine/PatchEngine.kt @@ -29,6 +29,12 @@ import java.util.logging.Logger /* * Single patching pipeline shared by CLI and GUI. (Eventually. Right now we are still having 2 pipelines) */ + +private const val DEFAULT_KEYSTORE_ALIAS = "Morphe" +private const val DEFAULT_KEYSTORE_PASSWORD = "Morphe" +private const val LEGACY_KEYSTORE_ALIAS = "Morphe Key" +private const val LEGACY_KEYSTORE_PASSWORD = "" + object PatchEngine { enum class PatchStep { @@ -231,8 +237,8 @@ object PatchEngine { val keystoreDetails = config.keystoreDetails ?: ApkUtils.KeyStoreDetails( File(tempDir, "morphe.keystore"), null, - "Morphe", - "Morphe", + DEFAULT_KEYSTORE_ALIAS, + DEFAULT_KEYSTORE_PASSWORD, ) try { ApkUtils.signApk( @@ -249,8 +255,8 @@ object PatchEngine { val legacyKeystoreDetails = ApkUtils.KeyStoreDetails( keystoreDetails.keyStore, null, - "Morphe Key", - "", + LEGACY_KEYSTORE_ALIAS, + LEGACY_KEYSTORE_PASSWORD, ) ApkUtils.signApk( From f5e52ac42072c6414c5e4b9930997856c4bb4911 Mon Sep 17 00:00:00 2001 From: Prateek <129204458+prateek-who@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:08:31 +0530 Subject: [PATCH 4/5] centralize keystore constants in PatchEngine.Config --- .../app/morphe/cli/command/PatchCommand.kt | 16 +++++--------- .../kotlin/app/morphe/engine/PatchEngine.kt | 22 +++++++++++-------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt index 6c344c9..763b031 100644 --- a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt @@ -20,6 +20,7 @@ import app.morphe.cli.command.model.toPatchBundle import app.morphe.cli.command.model.toSerializablePatch import app.morphe.cli.command.model.withUpdatedBundle import app.morphe.engine.ApkLibraryStripper +import app.morphe.engine.PatchEngine import app.morphe.engine.UpdateChecker import app.morphe.patcher.apk.ApkUtils import app.morphe.patcher.apk.ApkUtils.applyTo @@ -47,11 +48,6 @@ import java.io.StringWriter import java.util.concurrent.Callable import java.util.logging.Logger -private const val DEFAULT_KEYSTORE_ALIAS = "Morphe" -private const val DEFAULT_KEYSTORE_PASSWORD = "Morphe" -private const val LEGACY_KEYSTORE_ALIAS = "Morphe Key" -private const val LEGACY_KEYSTORE_PASSWORD = "" - @OptIn(ExperimentalSerializationApi::class) @VisibleForTesting @CommandLine.Command( @@ -201,13 +197,13 @@ internal object PatchCommand : Callable { description = ["Alias of the private key and certificate pair keystore entry."], showDefaultValue = ALWAYS, ) - private var keyStoreEntryAlias = DEFAULT_KEYSTORE_ALIAS // Default now matches Manager + private var keyStoreEntryAlias = PatchEngine.Config.DEFAULT_KEYSTORE_ALIAS // Default now matches Manager @CommandLine.Option( names = ["--keystore-entry-password"], description = ["Password of the keystore entry."], ) - private var keyStoreEntryPassword = DEFAULT_KEYSTORE_PASSWORD // Default now matches Manager + private var keyStoreEntryPassword = PatchEngine.Config.DEFAULT_KEYSTORE_PASSWORD // Default now matches Manager @CommandLine.Option( names = ["--signer"], @@ -681,7 +677,7 @@ internal object PatchCommand : Callable { ) } catch (e: Exception){ // We retry with legacy keystore defaults here. Need to move to new defaults eventually! - if (keyStoreEntryAlias == DEFAULT_KEYSTORE_ALIAS && keyStoreEntryPassword == DEFAULT_KEYSTORE_PASSWORD && keystoreFilePath.exists()){ + if (keyStoreEntryAlias == PatchEngine.Config.DEFAULT_KEYSTORE_ALIAS && keyStoreEntryPassword == PatchEngine.Config.DEFAULT_KEYSTORE_PASSWORD && keystoreFilePath.exists()){ logger.info("Retrying with legacy keystore credentials...") ApkUtils.signApk( @@ -691,8 +687,8 @@ internal object PatchCommand : Callable { ApkUtils.KeyStoreDetails( keystoreFilePath, keyStorePassword, - LEGACY_KEYSTORE_ALIAS, - LEGACY_KEYSTORE_PASSWORD, + PatchEngine.Config.LEGACY_KEYSTORE_ALIAS, + PatchEngine.Config.LEGACY_KEYSTORE_PASSWORD, ) ) } else { diff --git a/src/main/kotlin/app/morphe/engine/PatchEngine.kt b/src/main/kotlin/app/morphe/engine/PatchEngine.kt index d0870de..1ad4ccf 100644 --- a/src/main/kotlin/app/morphe/engine/PatchEngine.kt +++ b/src/main/kotlin/app/morphe/engine/PatchEngine.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext +import kotlinx.serialization.internal.NamedCompanion import java.io.File import java.io.PrintWriter import java.io.StringWriter @@ -30,10 +31,6 @@ import java.util.logging.Logger * Single patching pipeline shared by CLI and GUI. (Eventually. Right now we are still having 2 pipelines) */ -private const val DEFAULT_KEYSTORE_ALIAS = "Morphe" -private const val DEFAULT_KEYSTORE_PASSWORD = "Morphe" -private const val LEGACY_KEYSTORE_ALIAS = "Morphe Key" -private const val LEGACY_KEYSTORE_PASSWORD = "" object PatchEngine { @@ -59,7 +56,14 @@ object PatchEngine { val aaptBinaryPath: File? = null, val tempDir: File? = null, val failOnError: Boolean = true, - ) + ) { + companion object { + internal const val DEFAULT_KEYSTORE_ALIAS = "Morphe" + internal const val DEFAULT_KEYSTORE_PASSWORD = "Morphe" + internal const val LEGACY_KEYSTORE_ALIAS = "Morphe Key" + internal const val LEGACY_KEYSTORE_PASSWORD = "" + } + } data class Result( val success: Boolean, @@ -237,8 +241,8 @@ object PatchEngine { val keystoreDetails = config.keystoreDetails ?: ApkUtils.KeyStoreDetails( File(tempDir, "morphe.keystore"), null, - DEFAULT_KEYSTORE_ALIAS, - DEFAULT_KEYSTORE_PASSWORD, + Config.DEFAULT_KEYSTORE_ALIAS, + Config.DEFAULT_KEYSTORE_PASSWORD, ) try { ApkUtils.signApk( @@ -255,8 +259,8 @@ object PatchEngine { val legacyKeystoreDetails = ApkUtils.KeyStoreDetails( keystoreDetails.keyStore, null, - LEGACY_KEYSTORE_ALIAS, - LEGACY_KEYSTORE_PASSWORD, + Config.LEGACY_KEYSTORE_ALIAS, + Config.LEGACY_KEYSTORE_PASSWORD, ) ApkUtils.signApk( From 48e824a3e2e7f0cb6587603533d82f0cce21a2c9 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:35:15 +0200 Subject: [PATCH 5/5] refactor --- .../app/morphe/cli/command/PatchCommand.kt | 53 ++++++++++--------- .../kotlin/app/morphe/engine/PatchEngine.kt | 32 ++++++----- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt index a806935..4f74c7c 100644 --- a/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt +++ b/src/main/kotlin/app/morphe/cli/command/PatchCommand.kt @@ -20,13 +20,22 @@ import app.morphe.cli.command.model.toPatchBundle import app.morphe.cli.command.model.toSerializablePatch import app.morphe.cli.command.model.withUpdatedBundle import app.morphe.engine.PatchEngine +import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_KEYSTORE_ALIAS +import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_KEYSTORE_PASSWORD +import app.morphe.engine.PatchEngine.Config.Companion.LEGACY_KEYSTORE_ALIAS +import app.morphe.engine.PatchEngine.Config.Companion.LEGACY_KEYSTORE_PASSWORD import app.morphe.engine.UpdateChecker -import app.morphe.patcher.apk.ApkUtils -import app.morphe.patcher.apk.ApkUtils.applyTo -import app.morphe.library.installation.installer.* +import app.morphe.library.installation.installer.AdbInstaller +import app.morphe.library.installation.installer.AdbInstallerResult +import app.morphe.library.installation.installer.AdbRootInstaller +import app.morphe.library.installation.installer.DeviceNotFoundException +import app.morphe.library.installation.installer.Installer +import app.morphe.library.installation.installer.RootInstallerResult import app.morphe.patcher.Patcher import app.morphe.patcher.PatcherConfig import app.morphe.patcher.apk.ApkMerger +import app.morphe.patcher.apk.ApkUtils +import app.morphe.patcher.apk.ApkUtils.applyTo import app.morphe.patcher.logging.toMorpheLogger import app.morphe.patcher.patch.Patch import app.morphe.patcher.patch.loadPatchesFromJar @@ -197,13 +206,13 @@ internal object PatchCommand : Callable { description = ["Alias of the private key and certificate pair keystore entry."], showDefaultValue = ALWAYS, ) - private var keyStoreEntryAlias = PatchEngine.Config.DEFAULT_KEYSTORE_ALIAS // Default now matches Manager + private var keyStoreEntryAlias = PatchEngine.Config.DEFAULT_KEYSTORE_ALIAS @CommandLine.Option( names = ["--keystore-entry-password"], description = ["Password of the keystore entry."], ) - private var keyStoreEntryPassword = PatchEngine.Config.DEFAULT_KEYSTORE_PASSWORD // Default now matches Manager + private var keyStoreEntryPassword = PatchEngine.Config.DEFAULT_KEYSTORE_PASSWORD @CommandLine.Option( names = ["--signer"], @@ -667,7 +676,7 @@ internal object PatchCommand : Callable { patchingResult.addStepResult( PatchingStep.SIGNING, { - try { + fun signApk(alias: String, password: String) { ApkUtils.signApk( patchedApkFile, outputFilePath, @@ -675,26 +684,22 @@ internal object PatchCommand : Callable { ApkUtils.KeyStoreDetails( keystoreFilePath, keyStorePassword, - keyStoreEntryAlias, - keyStoreEntryPassword, - ), + alias, + password, + ) ) + } + try { + signApk(keyStoreEntryAlias, keyStoreEntryPassword) } catch (e: Exception){ - // We retry with legacy keystore defaults here. Need to move to new defaults eventually! - if (keyStoreEntryAlias == PatchEngine.Config.DEFAULT_KEYSTORE_ALIAS && keyStoreEntryPassword == PatchEngine.Config.DEFAULT_KEYSTORE_PASSWORD && keystoreFilePath.exists()){ - logger.info("Retrying with legacy keystore credentials...") - - ApkUtils.signApk( - patchedApkFile, - outputFilePath, - signer, - ApkUtils.KeyStoreDetails( - keystoreFilePath, - keyStorePassword, - PatchEngine.Config.LEGACY_KEYSTORE_ALIAS, - PatchEngine.Config.LEGACY_KEYSTORE_PASSWORD, - ) - ) + // Retry with legacy keystore defaults. + if (keyStoreEntryAlias == DEFAULT_KEYSTORE_ALIAS && + keyStoreEntryPassword == DEFAULT_KEYSTORE_PASSWORD && + keystoreFilePath.exists() + ) { + logger.info("Using legacy keystore credentials") + + signApk(LEGACY_KEYSTORE_ALIAS, LEGACY_KEYSTORE_PASSWORD) } else { throw e } diff --git a/src/main/kotlin/app/morphe/engine/PatchEngine.kt b/src/main/kotlin/app/morphe/engine/PatchEngine.kt index d7a60cf..ec6c9fa 100644 --- a/src/main/kotlin/app/morphe/engine/PatchEngine.kt +++ b/src/main/kotlin/app/morphe/engine/PatchEngine.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext -import kotlinx.serialization.internal.NamedCompanion import java.io.File import java.io.PrintWriter import java.io.StringWriter @@ -225,23 +224,28 @@ object PatchEngine { if (!config.unsigned) { onProgress("Signing APK...") try { + fun signApk(details: ApkUtils.KeyStoreDetails) { + ApkUtils.signApk( + rebuiltApk, + tempOutput, + config.signerName, + details, + ) + } + val keystoreDetails = config.keystoreDetails ?: ApkUtils.KeyStoreDetails( File(tempDir, "morphe.keystore"), null, Config.DEFAULT_KEYSTORE_ALIAS, Config.DEFAULT_KEYSTORE_PASSWORD, ) + try { - ApkUtils.signApk( - rebuiltApk, - tempOutput, - config.signerName, - keystoreDetails, - ) + signApk(keystoreDetails) } catch (e: Exception){ - // We retry with legacy keystore defaults here. Need to move to new defaults eventually! - if (config.keystoreDetails == null && keystoreDetails.keyStore.exists()){ - onProgress("Retrying with legacy keystore credentials...") + // Retry with legacy keystore defaults. + if (config.keystoreDetails == null && keystoreDetails.keyStore.exists()) { + logger.info("Using legacy keystore credentials") val legacyKeystoreDetails = ApkUtils.KeyStoreDetails( keystoreDetails.keyStore, @@ -249,13 +253,7 @@ object PatchEngine { Config.LEGACY_KEYSTORE_ALIAS, Config.LEGACY_KEYSTORE_PASSWORD, ) - - ApkUtils.signApk( - rebuiltApk, - tempOutput, - config.signerName, - legacyKeystoreDetails, - ) + signApk(legacyKeystoreDetails) } else { throw e }