Skip to content

Commit bae8b72

Browse files
chore: Merge branch dev to main (#161)
2 parents 208d64b + 170ad26 commit bae8b72

8 files changed

Lines changed: 106 additions & 55 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
## [1.9.1-dev.3](https://github.com/MorpheApp/morphe-cli/compare/v1.9.1-dev.2...v1.9.1-dev.3) (2026-06-02)
2+
3+
4+
### Bug Fixes
5+
6+
* signing improvements ([#160](https://github.com/MorpheApp/morphe-cli/issues/160)) ([166f940](https://github.com/MorpheApp/morphe-cli/commit/166f9409b1cbe00af7663545c41548ead2c189c5))
7+
8+
## [1.9.1-dev.2](https://github.com/MorpheApp/morphe-cli/compare/v1.9.1-dev.1...v1.9.1-dev.2) (2026-05-31)
9+
10+
11+
### Bug Fixes
12+
13+
* Update dependencies ([83d3969](https://github.com/MorpheApp/morphe-cli/commit/83d39692541ca81b7bb555dfd60a001fbb97b3f1))
14+
15+
## [1.9.1-dev.1](https://github.com/MorpheApp/morphe-cli/compare/v1.9.0...v1.9.1-dev.1) (2026-05-31)
16+
17+
18+
### Bug Fixes
19+
20+
* Update to latest ARSCLib ([f62a179](https://github.com/MorpheApp/morphe-cli/commit/f62a1793601fcfc489f54c558265115530ab6b8d))
21+
122
# [1.9.0](https://github.com/MorpheApp/morphe-cli/compare/v1.8.1...v1.9.0) (2026-05-29)
223

324

docs/1_usage.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ You can combine the option `-e`, `-d`, `--ei`, `--di` and `--exclusive`. Here is
5454
java -jar morphe-cli.jar patch --patches patches.mpp --exclusive -e "Patch name" --ei 123 input.apk
5555
```
5656

57+
You can also use multiple MPP files. Enable/disable and other bundle specific arguments are applied to the last `--patches` argument:
58+
59+
```bash
60+
java -jar morphe-cli.jar patch --patches patches-a.mpp -e "patch a" --patches patches-b.mpp -e "patch b" input.apk
61+
```
62+
5763

5864
> [!TIP]
5965
> You can use the option `-i` to automatically install the patched app after patching.

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# 💻 Documentation and guides of Morphe CLI
22

3-
This documentation contains topics around [Morphe CLI](https://github.com/MorpheApp/morphes-cli).
3+
This documentation contains topics around [Morphe CLI](https://github.com/MorpheApp/morphe-cli).
44

55
## 📖 Table of contents
66

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
org.gradle.parallel = true
22
org.gradle.caching = true
33
kotlin.code.style = official
4-
version = 1.9.0
4+
version = 1.9.1-dev.3

gradle/libs.versions.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ kotlin = "2.3.21"
55

66
# CLI
77
picocli = "4.7.7"
8-
arsclib = "d78a66bcee"
9-
morphe-patcher = "1.5.1"
8+
arsclib = "a28c6fb2a7"
9+
morphe-patcher = "1.5.2-dev.2" # TODO: Change to stable release
1010
morphe-library = "1.3.0"
1111

1212
# Compose Desktop
1313
compose = "1.10.3"
1414

1515
# Networking
16-
ktor = "3.4.3"
16+
ktor = "3.5.0"
1717

1818
# DI
1919
koin-bom = "4.2.1"
@@ -29,7 +29,7 @@ kotlinx-serialization = "1.11.0"
2929
jna = "5.18.1"
3030

3131
# Testing
32-
mockk = "1.14.9"
32+
mockk = "1.14.11"
3333

3434
# Logging
3535
slf4j = "2.0.18"

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

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ import app.morphe.engine.isWindows
1515
import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_KEYSTORE_ALIAS
1616
import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_KEYSTORE_PASSWORD
1717
import app.morphe.engine.PatchEngine.Config.Companion.DEFAULT_SIGNER_NAME
18-
import app.morphe.engine.PatchEngine.Config.Companion.LEGACY_KEYSTORE_ALIAS
19-
import app.morphe.engine.PatchEngine.Config.Companion.LEGACY_KEYSTORE_PASSWORD
2018
import app.morphe.engine.UpdateChecker
19+
import app.morphe.engine.util.signWithLegacyFallback
2120
import app.morphe.engine.patches.LoadedBundle
2221
import app.morphe.engine.patches.PatchBundleLoader
2322
import app.morphe.library.installation.installer.*
@@ -836,33 +835,18 @@ internal object PatchCommand : Callable<Int> {
836835
patchingResult.addStepResult(
837836
PatchingStep.SIGNING,
838837
{
839-
fun signApk(alias: String, password: String) {
840-
ApkUtils.signApk(
841-
patchedApkFile,
842-
outputFilePath,
843-
signer,
844-
ApkUtils.KeyStoreDetails(
845-
keystoreFilePath,
846-
keyStorePassword,
847-
alias,
848-
password,
849-
)
850-
)
851-
}
852-
try {
853-
signApk(keyStoreEntryAlias, keyStoreEntryPassword)
854-
} catch (e: Exception){
855-
// Retry with legacy keystore defaults.
856-
if (keyStoreEntryAlias == DEFAULT_KEYSTORE_ALIAS &&
857-
keyStoreEntryPassword == DEFAULT_KEYSTORE_PASSWORD &&
858-
keystoreFilePath.exists()
859-
) {
860-
logger.info("Using legacy keystore credentials")
861-
862-
signApk(LEGACY_KEYSTORE_ALIAS, LEGACY_KEYSTORE_PASSWORD)
863-
} else {
864-
throw e
865-
}
838+
signWithLegacyFallback(
839+
primary = ApkUtils.KeyStoreDetails(
840+
keystoreFilePath,
841+
keyStorePassword,
842+
keyStoreEntryAlias,
843+
keyStoreEntryPassword,
844+
),
845+
allowLegacyFallback = keyStoreEntryAlias == DEFAULT_KEYSTORE_ALIAS &&
846+
keyStoreEntryPassword == DEFAULT_KEYSTORE_PASSWORD,
847+
logger = logger,
848+
) { details ->
849+
ApkUtils.signApk(patchedApkFile, outputFilePath, signer, details)
866850
}
867851
}
868852
)

src/main/kotlin/app/morphe/engine/PatchEngine.kt

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package app.morphe.engine
1010

11+
import app.morphe.engine.util.signWithLegacyFallback
1112
import app.morphe.patcher.Patcher
1213
import app.morphe.patcher.PatcherConfig
1314
import app.morphe.patcher.apk.ApkMerger
@@ -244,33 +245,18 @@ object PatchEngine {
244245
}
245246

246247
try {
247-
fun signApk(details: ApkUtils.KeyStoreDetails) {
248+
signWithLegacyFallback(
249+
primary = keystoreDetails,
250+
allowLegacyFallback = config.keystoreDetails == null,
251+
logger = logger,
252+
) { details ->
248253
ApkUtils.signApk(
249254
rebuiltApk,
250255
tempOutput,
251256
config.signerName,
252257
details,
253258
)
254259
}
255-
256-
try {
257-
signApk(keystoreDetails)
258-
} catch (e: Exception) {
259-
// Retry with legacy keystore defaults.
260-
if (config.keystoreDetails == null && keystoreDetails.keyStore.exists()) {
261-
logger.info("Using legacy keystore credentials")
262-
263-
val legacyKeystoreDetails = ApkUtils.KeyStoreDetails(
264-
keystoreDetails.keyStore,
265-
null,
266-
Config.LEGACY_KEYSTORE_ALIAS,
267-
Config.LEGACY_KEYSTORE_PASSWORD,
268-
)
269-
signApk(legacyKeystoreDetails)
270-
} else {
271-
throw e
272-
}
273-
}
274260
stepResults.add(StepResult(PatchStep.SIGNING, true))
275261
} catch (e: Exception) {
276262
stepResults.add(StepResult(PatchStep.SIGNING, false, e.toString()))
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2026 Morphe.
3+
* https://github.com/MorpheApp/morphe-cli
4+
*/
5+
6+
package app.morphe.engine.util
7+
8+
import app.morphe.engine.PatchEngine
9+
import app.morphe.patcher.apk.ApkUtils
10+
import java.util.logging.Logger
11+
12+
/**
13+
* Signs an APK with [primary] credentials, falling back to the legacy ("Morphe Key" / empty password) entry.
14+
* The legacy retry only fires when [allowLegacyFallback] is true AND the keystore file already exists,
15+
* i.e. the user is on default credentials and we're reading a pre-existing keystore that might predate the current alias.
16+
* This preserves the exact condition both call sites (CLI + engine) used before.
17+
*
18+
* On double failure the PRIMARY exception is thrown (legacy attached as suppressed).
19+
* The primary error is the meaningful one: the user expects the current Morphe key,
20+
* so "no 'Morphe' entry" is more actionable than whatever the legacy retry hit.
21+
* The old behavior threw the *legacy* failure, which surfaced confusing errors.
22+
*
23+
* [sign] performs the actual signing; callers wrap this call with their own progress / step-result reporting.
24+
*/
25+
fun signWithLegacyFallback(
26+
primary: ApkUtils.KeyStoreDetails,
27+
allowLegacyFallback: Boolean,
28+
logger: Logger,
29+
sign: (ApkUtils.KeyStoreDetails) -> Unit,
30+
) {
31+
try {
32+
sign(primary)
33+
} catch (primaryError: Exception) {
34+
if (!allowLegacyFallback || !primary.keyStore.exists()) throw primaryError
35+
36+
// Never silently swallow the real cause. Always log it before the back-compat path.
37+
logger.info(
38+
"Default keystore credentials failed (${primaryError.message}). Retrying with legacy credentials"
39+
)
40+
41+
val legacy = ApkUtils.KeyStoreDetails(
42+
primary.keyStore,
43+
primary.keyStorePassword,
44+
PatchEngine.Config.LEGACY_KEYSTORE_ALIAS,
45+
PatchEngine.Config.LEGACY_KEYSTORE_PASSWORD,
46+
)
47+
try {
48+
sign(legacy)
49+
} catch (legacyError: Exception) {
50+
primaryError.addSuppressed(legacyError)
51+
throw primaryError
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)