mirror of
https://github.com/revanced/revanced-cli.git
synced 2024-11-19 01:59:25 +01:00
feat: load patches dynamically & use kotlinx.cli
Patches are now loaded dynamically and the CLI now links to the patches library. Also decided to use the CLI library from kotlinx, since that's friendlier than whatever we had before.
This commit is contained in:
parent
e50071aa61
commit
4624384f28
@ -18,11 +18,14 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
val patchesDependency = "app.revanced:revanced-patches:1.0.0-dev.4"
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.4")
|
||||
|
||||
implementation("app.revanced:revanced-patcher:1.0.0-dev.8")
|
||||
implementation("app.revanced:revanced-patches:1.0.0-dev.4")
|
||||
implementation(patchesDependency)
|
||||
|
||||
implementation("com.google.code.gson:gson:2.9.0")
|
||||
}
|
||||
@ -32,8 +35,15 @@ tasks {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
shadowJar {
|
||||
dependencies {
|
||||
// This makes sure we link to the library, but don't include it.
|
||||
// So, a "runtime only" dependency.
|
||||
exclude(dependency(patchesDependency))
|
||||
}
|
||||
manifest {
|
||||
attributes(Pair("Main-Class", "app.revanced.cli.MainKt"))
|
||||
attributes("Main-Class" to "app.revanced.cli.Main")
|
||||
attributes("Implementation-Title" to project.name)
|
||||
attributes("Implementation-Version" to project.version)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,92 @@
|
||||
package app.revanced.cli
|
||||
|
||||
import app.revanced.cli.utils.PatchLoader
|
||||
import app.revanced.cli.utils.Patches
|
||||
import app.revanced.cli.utils.Preconditions
|
||||
import app.revanced.cli.utils.SignatureParser
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patches.Index.patches
|
||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlinx.cli.required
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
private const val CLI_NAME = "ReVanced CLI"
|
||||
private val CLI_VERSION = Main::class.java.`package`.implementationVersion ?: "0.0.0-unknown"
|
||||
|
||||
class Main {
|
||||
companion object {
|
||||
private fun runCLI(
|
||||
inApk: String,
|
||||
inSignatures: String,
|
||||
inPatches: String,
|
||||
inOutput: String,
|
||||
) {
|
||||
val apk = Preconditions.isFile(inApk)
|
||||
val signatures = Preconditions.isFile(inSignatures)
|
||||
val patchesFile = Preconditions.isFile(inPatches)
|
||||
val output = Preconditions.isDirectory(inOutput)
|
||||
|
||||
val patcher = Patcher(
|
||||
File(args[0]), // in.apk
|
||||
SignatureParser.parse(args[2]).toTypedArray() // signatures.json
|
||||
apk,
|
||||
SignatureParser
|
||||
.parse(signatures.readText())
|
||||
.toTypedArray()
|
||||
)
|
||||
|
||||
// add integrations dex container
|
||||
patcher.addFiles(File(args[3]))
|
||||
PatchLoader.injectPatches(patchesFile)
|
||||
val patches = Patches.loadPatches()
|
||||
patcher.addPatches(*patches.map { it() }.toTypedArray())
|
||||
|
||||
for (patch in patches) {
|
||||
patcher.addPatches(patch())
|
||||
}
|
||||
|
||||
patcher.applyPatches().forEach { (name, result) ->
|
||||
val results = patcher.applyPatches()
|
||||
for ((name, result) in results) {
|
||||
println("$name: $result")
|
||||
}
|
||||
|
||||
// save patched apk
|
||||
val dexFiles: Map<String, MemoryDataStore> = patcher.save()
|
||||
dexFiles.forEach { (t, p) ->
|
||||
Files.write(File(args[1], t).toPath(), p.buffer)
|
||||
val dexFiles = patcher.save()
|
||||
dexFiles.forEach { (dexName, dexData) ->
|
||||
Files.write(File(output, dexName).toPath(), dexData.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
println("$CLI_NAME version $CLI_VERSION")
|
||||
val parser = ArgParser(CLI_NAME)
|
||||
|
||||
val apk by parser.option(
|
||||
ArgType.String,
|
||||
fullName = "apk",
|
||||
shortName = "a",
|
||||
description = "APK file"
|
||||
).required()
|
||||
val signatures by parser.option(
|
||||
ArgType.String,
|
||||
fullName = "signatures",
|
||||
shortName = "s",
|
||||
description = "Signatures JSON file"
|
||||
).required()
|
||||
val patches by parser.option(
|
||||
ArgType.String,
|
||||
fullName = "patches",
|
||||
shortName = "p",
|
||||
description = "Patches JAR file"
|
||||
).required()
|
||||
val output by parser.option(
|
||||
ArgType.String,
|
||||
fullName = "output",
|
||||
shortName = "o",
|
||||
description = "Output directory"
|
||||
).required()
|
||||
// TODO: merge dex file
|
||||
|
||||
parser.parse(args)
|
||||
runCLI(
|
||||
apk,
|
||||
signatures,
|
||||
patches,
|
||||
output,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
25
src/main/kotlin/app/revanced/cli/utils/PatchLoader.kt
Normal file
25
src/main/kotlin/app/revanced/cli/utils/PatchLoader.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package app.revanced.cli.utils
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.net.URLClassLoader
|
||||
|
||||
class PatchLoader {
|
||||
companion object {
|
||||
fun injectPatches(file: File) {
|
||||
// This function will fail on Java 9 and above.
|
||||
try {
|
||||
val url = file.toURI().toURL()
|
||||
val classLoader = Thread.currentThread().contextClassLoader as URLClassLoader
|
||||
val method = URLClassLoader::class.java.getDeclaredMethod("addURL", URL::class.java)
|
||||
method.isAccessible = true
|
||||
method.invoke(classLoader, url)
|
||||
} catch (e: Exception) {
|
||||
throw Exception(
|
||||
"Failed to inject patches! The CLI does NOT work on Java 9 and above, please use Java 8!",
|
||||
e // propagate exception
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/main/kotlin/app/revanced/cli/utils/Patches.kt
Normal file
15
src/main/kotlin/app/revanced/cli/utils/Patches.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package app.revanced.cli.utils
|
||||
|
||||
import app.revanced.patches.Index
|
||||
|
||||
class Patches {
|
||||
companion object {
|
||||
// You may ask yourself, "why do this?".
|
||||
// We do it like this, because we don't want the Index class
|
||||
// to be loaded while the dependency hasn't been injected yet.
|
||||
// You can see this as "controlled class loading".
|
||||
// Whenever this class is loaded (because it is invoked), all the imports
|
||||
// will be loaded too. We don't want to do this until we've injected the class.
|
||||
fun loadPatches() = Index.patches
|
||||
}
|
||||
}
|
24
src/main/kotlin/app/revanced/cli/utils/Preconditions.kt
Normal file
24
src/main/kotlin/app/revanced/cli/utils/Preconditions.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package app.revanced.cli.utils
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class Preconditions {
|
||||
companion object {
|
||||
fun isFile(path: String): File {
|
||||
val f = File(path)
|
||||
if (!f.exists()) {
|
||||
throw FileNotFoundException(f.toString())
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
fun isDirectory(path: String): File {
|
||||
val f = isFile(path)
|
||||
if (!f.isDirectory) {
|
||||
throw IllegalArgumentException("$f is not a directory")
|
||||
}
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
@ -4,19 +4,15 @@ import app.revanced.patcher.signature.MethodSignature
|
||||
import com.google.gson.JsonParser
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import java.io.File
|
||||
|
||||
class SignatureParser {
|
||||
companion object {
|
||||
fun parse(signatureJsonPath: String): List<MethodSignature> {
|
||||
val json = File(signatureJsonPath).readText()
|
||||
fun parse(json: String): List<MethodSignature> {
|
||||
val signatures = JsonParser.parseString(json).asJsonObject.get("signatures").asJsonArray.map { sig ->
|
||||
val signature = sig.asJsonObject
|
||||
|
||||
val returnType = signature.get("returns").asString
|
||||
|
||||
var accessFlags = 0
|
||||
|
||||
signature
|
||||
.get("accessors").asJsonArray
|
||||
.forEach { accessFlags = accessFlags or AccessFlags.getAccessFlag(it.asString).value }
|
||||
|
Loading…
Reference in New Issue
Block a user