Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
07c3f7e
feat: Add ARSCLib support (#55)
wchill Feb 21, 2026
3080da5
chore: Release v1.5.0-dev.1 [skip ci]
semantic-release-bot Feb 21, 2026
f8bd235
fix: Use latest patcher dev release
LisoUseInAIKyrios Feb 24, 2026
286fc82
chore: Release v1.5.0-dev.2 [skip ci]
semantic-release-bot Feb 24, 2026
7cc195a
fix: Use latest patcher dev release
LisoUseInAIKyrios Feb 25, 2026
d882ec1
chore: Release v1.5.0-dev.3 [skip ci]
semantic-release-bot Feb 25, 2026
791257c
fix: Use latest patcher dev release
LisoUseInAIKyrios Feb 26, 2026
92c96f3
chore: Release v1.5.0-dev.4 [skip ci]
semantic-release-bot Feb 26, 2026
af85e25
ci: Fix build artifact name
LisoUseInAIKyrios Feb 26, 2026
ef9fc48
feat: Add `--out` and `--patches` arguments to `list-patches` command…
prateek-who Feb 26, 2026
5691f79
chore: Release v1.5.0-dev.5 [skip ci]
semantic-release-bot Feb 26, 2026
b60ea86
fix: Use latest library dev release
LisoUseInAIKyrios Feb 27, 2026
595b518
chore: Release v1.5.0-dev.6 [skip ci]
semantic-release-bot Feb 27, 2026
168916d
ci: Run tests on Windows and macOS (#64)
wchill Feb 28, 2026
2f21880
fix: Use latest patcher and library dev release
wchill Feb 28, 2026
7b00273
chore: Release v1.5.0-dev.7 [skip ci]
semantic-release-bot Feb 28, 2026
7b6434d
chore(deps): bump actions/upload-artifact from 6 to 7 (#65)
dependabot[bot] Mar 1, 2026
408b16d
ci: Don't zip uploaded artifacts
LisoUseInAIKyrios Mar 1, 2026
9cf07f9
fix: Use latest patcher dev release
LisoUseInAIKyrios Mar 2, 2026
cb02087
chore: Release v1.5.0-dev.8 [skip ci]
semantic-release-bot Mar 2, 2026
baf34de
fix: Use latest patcher dev release
LisoUseInAIKyrios Mar 2, 2026
a9f0158
chore: Release v1.5.0-dev.9 [skip ci]
semantic-release-bot Mar 2, 2026
2b33a6c
fix: Use latest patcher dev release
LisoUseInAIKyrios Mar 5, 2026
626b484
chore: Release v1.5.0-dev.10 [skip ci]
semantic-release-bot Mar 5, 2026
ea6e231
fix: Use latest patcher dev release
wchill Mar 5, 2026
bc84138
chore: Release v1.5.0-dev.11 [skip ci]
semantic-release-bot Mar 5, 2026
60e0580
chore: Use stable release
LisoUseInAIKyrios Mar 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/build_pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ on:
- dev

jobs:
release:
name: Build
runs-on: ubuntu-latest
build:
name: Build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -20,13 +24,15 @@ jobs:
uses: burrunan/gradle-cache-action@v1

- name: Build
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --no-daemon

- name: Upload artifacts
uses: actions/upload-artifact@v6
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v7
with:
name: morphe-patches
archive: false
path: build/libs/morphe-cli-*-all.jar

2 changes: 1 addition & 1 deletion .releaserc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
{
"assets": [
{
"path": "build/libs/*-all*"
"path": "build/libs/morphe-cli*-all.jar"
}
],
successComment: false
Expand Down
77 changes: 77 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,80 @@
# [1.5.0-dev.11](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.10...v1.5.0-dev.11) (2026-03-05)


### Bug Fixes

* Use latest patcher dev release ([ea6e231](https://github.com/MorpheApp/morphe-cli/commit/ea6e2313268a8111c8a21faf425bda1eda534f35))

# [1.5.0-dev.10](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.9...v1.5.0-dev.10) (2026-03-05)


### Bug Fixes

* Use latest patcher dev release ([2b33a6c](https://github.com/MorpheApp/morphe-cli/commit/2b33a6cc0523be29cb7dcd86990de7e0e08c0c87))

# [1.5.0-dev.9](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.8...v1.5.0-dev.9) (2026-03-02)


### Bug Fixes

* Use latest patcher dev release ([baf34de](https://github.com/MorpheApp/morphe-cli/commit/baf34de0857f1c94b044aedbac33485b51ab3f2c))

# [1.5.0-dev.8](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.7...v1.5.0-dev.8) (2026-03-02)


### Bug Fixes

* Use latest patcher dev release ([9cf07f9](https://github.com/MorpheApp/morphe-cli/commit/9cf07f922fb5129aeeee2de0a3e990fecadae4fb))

# [1.5.0-dev.7](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.6...v1.5.0-dev.7) (2026-02-28)


### Bug Fixes

* Use latest patcher and library dev release ([2f21880](https://github.com/MorpheApp/morphe-cli/commit/2f21880c49705b3e5153b7e636f01cf578d7b1c0))

# [1.5.0-dev.6](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.5...v1.5.0-dev.6) (2026-02-27)


### Bug Fixes

* Use latest library dev release ([b60ea86](https://github.com/MorpheApp/morphe-cli/commit/b60ea86c72b8a62e2220b3a76dba01d47c494750))

# [1.5.0-dev.5](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.4...v1.5.0-dev.5) (2026-02-26)


### Features

* Add `--out` and `--patches` arguments to `list-patches` command ([#60](https://github.com/MorpheApp/morphe-cli/issues/60)) ([ef9fc48](https://github.com/MorpheApp/morphe-cli/commit/ef9fc482fcc6de2e51741b23ce12729991d6b36d))

# [1.5.0-dev.4](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.3...v1.5.0-dev.4) (2026-02-26)


### Bug Fixes

* Use latest patcher dev release ([791257c](https://github.com/MorpheApp/morphe-cli/commit/791257c48983c1a006eca87abd458331141252f1))

# [1.5.0-dev.3](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.2...v1.5.0-dev.3) (2026-02-25)


### Bug Fixes

* Use latest patcher dev release ([7cc195a](https://github.com/MorpheApp/morphe-cli/commit/7cc195a59d37521751fcbe5f15d267b3efbeeb0a))

# [1.5.0-dev.2](https://github.com/MorpheApp/morphe-cli/compare/v1.5.0-dev.1...v1.5.0-dev.2) (2026-02-24)


### Bug Fixes

* Use latest patcher dev release ([f8bd235](https://github.com/MorpheApp/morphe-cli/commit/f8bd2354438dd3a1e969610d43dca224e9d3ef63))

# [1.5.0-dev.1](https://github.com/MorpheApp/morphe-cli/compare/v1.4.0...v1.5.0-dev.1) (2026-02-21)


### Features

* Add ARSCLib support ([#55](https://github.com/MorpheApp/morphe-cli/issues/55)) ([07c3f7e](https://github.com/MorpheApp/morphe-cli/commit/07c3f7ec50d52739ee2695f52c3c7182f2287ecf))

# [1.4.0](https://github.com/MorpheApp/morphe-cli/compare/v1.3.0...v1.4.0) (2026-02-21)


Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ val strippedApkEditorLib by tasks.registering(org.gradle.jvm.tasks.Jar::class) {
}

dependencies {
implementation(libs.morphe.patcher)
api(libs.morphe.patcher)
implementation(libs.morphe.library)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
Expand All @@ -61,6 +61,7 @@ dependencies {
implementation(files(strippedApkEditorLib))

testImplementation(libs.kotlin.test)
testImplementation(libs.junit.params)
}

kotlin {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 1.4.0
version = 1.5.0-dev.11
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[versions]
shadow = "8.3.9"
junit = "5.11.0"
kotlin = "2.3.0"
kotlinx = "1.9.0"
picocli = "4.7.7"
morphe-patcher = "1.1.1"
morphe-library = "1.2.0"
morphe-patcher = "1.2.0"
morphe-library = "1.3.0"

[libraries]
junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" }
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx" }
Expand Down
24 changes: 10 additions & 14 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
rootProject.name = "morphe-cli"

// Include morphe-patcher and morphe-library as composite builds if they exist locally
val morphePatcherDir = file("../morphe-patcher")
if (morphePatcherDir.exists()) {
includeBuild(morphePatcherDir) {
dependencySubstitution {
substitute(module("app.morphe:morphe-patcher")).using(project(":"))
}
}
}

val morpheLibraryDir = file("../morphe-library")
if (morpheLibraryDir.exists()) {
includeBuild(morpheLibraryDir) {
dependencySubstitution {
substitute(module("app.morphe:morphe-library")).using(project(":"))
mapOf(
"morphe-patcher" to "app.morphe:morphe-patcher",
"morphe-library" to "app.morphe:morphe-library",
).forEach { (libraryPath, libraryName) ->
val libDir = file("../$libraryPath")
if (libDir.exists()) {
includeBuild(libDir) {
dependencySubstitution {
substitute(module(libraryName)).using(project(":"))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package app.morphe.cli.command

import app.morphe.library.PackageName
import app.morphe.library.VersionMap
import app.morphe.library.mostCommonCompatibleVersions
import app.morphe.patcher.patch.PackageName
import app.morphe.patcher.patch.VersionMap
import app.morphe.patcher.patch.loadPatchesFromJar
import app.morphe.patcher.patch.mostCommonCompatibleVersions
import picocli.CommandLine
import java.io.File
import java.util.logging.Logger
Expand Down
59 changes: 48 additions & 11 deletions src/main/kotlin/app/morphe/cli/command/ListPatchesCommand.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-cli
*
* Original hard forked code:
* https://github.com/revanced/revanced-cli
*/

package app.morphe.cli.command

import app.morphe.patcher.patch.Package
import app.morphe.patcher.patch.Patch
import app.morphe.patcher.patch.loadPatchesFromJar
import picocli.CommandLine.*
import picocli.CommandLine.Command
import picocli.CommandLine.Option
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
import java.util.logging.Logger
Expand All @@ -16,11 +25,20 @@ import app.morphe.patcher.patch.Option as PatchOption
internal object ListPatchesCommand : Runnable {
private val logger = Logger.getLogger(this::class.java.name)

@Parameters(
description = ["Paths to MPP files."],
// Patches is now flag based rather than position based
@Option(
names = ["--patches"],
description = ["One or more paths to MPP files."],
arity = "1..*",
required = true
)
private lateinit var patchFiles: Set<File>

@Option(
names = ["--out"],
description = ["Path to the output text file. Writes patch list to this file instead of stdout."],
)
private lateinit var patchesFiles: Set<File>
private var outputFile: File? = null

@Option(
names = ["-d", "--with-descriptions"],
Expand Down Expand Up @@ -135,14 +153,33 @@ internal object ListPatchesCommand : Runnable {
}

fun Patch<*>.filterCompatiblePackages(name: String) =
compatiblePackages?.any { (compatiblePackageName, _) -> compatiblePackageName == name }
?: withUniversalPatches
compatiblePackages?.any { (compatiblePackageName, _) ->
compatiblePackageName == name
} ?: withUniversalPatches

val patches = loadPatchesFromJar(patchesFiles).withIndex().toList()
val patches = loadPatchesFromJar(patchFiles).withIndex().toList()

val filtered =
packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches

if (filtered.isNotEmpty()) logger.info(filtered.joinToString("\n\n") { it.buildString() })
val filtered = packageName?.let {
patches.filter { (_, patch) ->
patch.filterCompatiblePackages(
it
)
}
} ?: patches

// Extracted the final output that we get into this variable. Now we just call this based
// on what the user wants. In the console or as an external text file.
val finalOutput = filtered.joinToString("\n\n") {it.buildString()}

if (filtered.isEmpty()) {
logger.warning("No compatible patches found in: $patchFiles")
} else {
if (outputFile == null) {
logger.info(finalOutput)
} else {
logger.info("Created new output file at ${outputFile!!.path}")
outputFile!!.writeText(finalOutput)
}
}
}
}
4 changes: 3 additions & 1 deletion src/main/kotlin/app/morphe/cli/command/MainCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app.morphe.cli.command

import app.morphe.cli.command.utility.UtilityCommand
import app.morphe.library.logging.Logger
import org.jetbrains.annotations.VisibleForTesting
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.IVersionProvider
Expand Down Expand Up @@ -42,4 +43,5 @@ private object CLIVersionProvider : IVersionProvider {
UtilityCommand::class,
]
)
private object MainCommand
@VisibleForTesting
internal object MainCommand
30 changes: 24 additions & 6 deletions src/main/kotlin/app/morphe/cli/command/PatchCommand.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-cli
*
* Original hard forked code:
* https://github.com/revanced/revanced-cli
*/

package app.morphe.cli.command

import app.morphe.cli.command.model.FailedPatch
Expand All @@ -12,20 +20,21 @@ import app.morphe.cli.command.model.toPatchBundle
import app.morphe.cli.command.model.toSerializablePatch
import app.morphe.cli.command.model.withUpdatedBundle
import app.morphe.gui.util.ApkLibraryStripper
import app.morphe.library.ApkUtils
import app.morphe.library.ApkUtils.applyTo
import app.morphe.patcher.apk.ApkUtils
import app.morphe.patcher.apk.ApkUtils.applyTo
import app.morphe.library.installation.installer.*
import app.morphe.library.setOptions
import app.morphe.patcher.Patcher
import app.morphe.patcher.PatcherConfig
import app.morphe.patcher.patch.Patch
import app.morphe.patcher.patch.loadPatchesFromJar
import app.morphe.patcher.patch.setOptions
import com.reandroid.apkeditor.merge.Merger
import com.reandroid.apkeditor.merge.MergerOptions
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import org.jetbrains.annotations.VisibleForTesting
import picocli.CommandLine
import picocli.CommandLine.ArgGroup
import picocli.CommandLine.Help.Visibility.ALWAYS
Expand All @@ -38,6 +47,7 @@ import java.util.concurrent.Callable
import java.util.logging.Logger

@OptIn(ExperimentalSerializationApi::class)
@VisibleForTesting
@CommandLine.Command(
name = "patch",
description = ["Patch an APK file."],
Expand Down Expand Up @@ -249,7 +259,7 @@ internal object PatchCommand : Callable<Int> {

@CommandLine.Option(
names = ["--custom-aapt2-binary"],
description = ["Path to a custom AAPT binary to compile resources with."],
description = ["Path to a custom AAPT binary to compile resources with. Only valid when --use-arsclib is not specified."],
)
@Suppress("unused")
private fun setAaptBinaryPath(aaptBinaryPath: File) {
Expand All @@ -262,6 +272,13 @@ internal object PatchCommand : Callable<Int> {
this.aaptBinaryPath = aaptBinaryPath
}

@CommandLine.Option(
names = ["--force-apktool"],
description = ["Use apktool instead of arsclib to compile resources. Implied if --custom-aapt2-binary is specified."],
showDefaultValue = ALWAYS,
)
private var forceApktool: Boolean = false

@CommandLine.Option(
names = ["--unsigned"],
description = ["Disable signing of the final apk."],
Expand Down Expand Up @@ -436,11 +453,12 @@ internal object PatchCommand : Callable<Int> {
inputApk,
patcherTemporaryFilesPath,
aaptBinaryPath?.path,
patcherTemporaryFilesPath.absolutePath
patcherTemporaryFilesPath.absolutePath,
if (aaptBinaryPath != null) { false } else { !forceApktool },
),
).use { patcher ->
val packageName = patcher.context.packageMetadata.packageName
val packageVersion = patcher.context.packageMetadata.packageVersion
val packageVersion = patcher.context.packageMetadata.versionName

patchingResult.packageName = packageName
patchingResult.packageVersion = packageVersion
Expand Down
Loading
Loading