mirror of
https://github.com/revanced/revanced-cli.git
synced 2024-11-03 18:33:55 +01:00
refactor: Move ReVanced Library subproject to another repository
This commit removes the subproject ReVanced Library and moves it to another repository. A monorepo turned out to be difficult to work with.
This commit is contained in:
parent
84b24f1b6f
commit
995f2ec99b
@ -31,7 +31,7 @@
|
|||||||
{
|
{
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
"path": "revanced-cli/build/libs/*all.jar"
|
"path": "build/libs/*all.jar"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
successComment: false
|
successComment: false
|
||||||
|
@ -1,7 +1,63 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.0" apply false
|
kotlin("jvm") version "1.9.0"
|
||||||
|
alias(libs.plugins.shadow)
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
group = "app.revanced"
|
||||||
group = "app.revanced"
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.revanced.patcher)
|
||||||
|
implementation(libs.revanced.library)
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.picocli)
|
||||||
|
|
||||||
|
testImplementation(libs.kotlin.test)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin { jvmToolchain(11) }
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
testLogging {
|
||||||
|
events("PASSED", "SKIPPED", "FAILED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
expand("projectVersion" to project.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
manifest {
|
||||||
|
attributes("Main-Class" to "app.revanced.cli.command.MainCommandKt")
|
||||||
|
}
|
||||||
|
minimize {
|
||||||
|
exclude(dependency("org.jetbrains.kotlin:.*"))
|
||||||
|
exclude(dependency("org.bouncycastle:.*"))
|
||||||
|
exclude(dependency("app.revanced:.*"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build {
|
||||||
|
dependsOn(shadowJar)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dummy task to hack gradle-semantic-release-plugin to release this project.
|
||||||
|
|
||||||
|
Explanation:
|
||||||
|
SemVer is a standard for versioning libraries.
|
||||||
|
For that reason the semantic-release plugin uses the "publish" task to publish libraries.
|
||||||
|
However, this subproject is not a library, and the "publish" task is not available for this subproject.
|
||||||
|
Because semantic-release is not designed to handle this case, we need to hack it.
|
||||||
|
|
||||||
|
RE: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||||
|
*/
|
||||||
|
|
||||||
|
register<DefaultTask>("publish") {
|
||||||
|
group = "publishing"
|
||||||
|
description = "Dummy task to hack gradle-semantic-release-plugin to release ReVanced CLI"
|
||||||
|
dependsOn(build)
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,27 +1,17 @@
|
|||||||
[versions]
|
[versions]
|
||||||
shadow = "8.1.1"
|
shadow = "8.1.1"
|
||||||
apksig = "8.1.1"
|
|
||||||
bcpkix-jdk18on = "1.76"
|
|
||||||
jackson-module-kotlin = "2.14.3"
|
|
||||||
jadb = "2531a28109"
|
|
||||||
kotlin-reflect = "1.9.0"
|
|
||||||
kotlin-test = "1.8.20-RC"
|
kotlin-test = "1.8.20-RC"
|
||||||
kotlinx-coroutines-core = "1.7.3"
|
kotlinx-coroutines-core = "1.7.3"
|
||||||
picocli = "4.7.3"
|
picocli = "4.7.3"
|
||||||
revanced-patcher = "15.0.1"
|
revanced-patcher = "15.0.1"
|
||||||
binary-compatibility-validator = "0.13.2"
|
revanced-library = "1.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
|
|
||||||
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkix-jdk18on" }
|
|
||||||
jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" }
|
|
||||||
jadb = { module = "com.github.revanced:jadb", version.ref = "jadb" }
|
|
||||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
|
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
||||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||||
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
|
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
|
||||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||||
|
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
||||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("jvm") version "1.9.0"
|
|
||||||
alias(libs.plugins.shadow)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(":revanced-lib"))
|
|
||||||
implementation(libs.revanced.patcher)
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
|
||||||
implementation(libs.picocli)
|
|
||||||
|
|
||||||
testImplementation(libs.kotlin.test)
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin { jvmToolchain(11) }
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events("PASSED", "SKIPPED", "FAILED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processResources {
|
|
||||||
expand("projectVersion" to project.version)
|
|
||||||
}
|
|
||||||
|
|
||||||
shadowJar {
|
|
||||||
manifest {
|
|
||||||
attributes("Main-Class" to "app.revanced.cli.command.MainCommandKt")
|
|
||||||
}
|
|
||||||
minimize {
|
|
||||||
exclude(dependency("org.jetbrains.kotlin:.*"))
|
|
||||||
exclude(dependency("org.bouncycastle:.*"))
|
|
||||||
exclude(dependency("app.revanced:.*"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build {
|
|
||||||
dependsOn(shadowJar)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
rootProject.name = "revanced-cli"
|
|
@ -1,140 +0,0 @@
|
|||||||
public final class app/revanced/lib/ApkSigner {
|
|
||||||
public static final field INSTANCE Lapp/revanced/lib/ApkSigner;
|
|
||||||
public final fun newApkSignerBuilder (Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
|
|
||||||
public final fun newApkSignerBuilder (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/android/apksig/ApkSigner$Builder;
|
|
||||||
public final fun newKeyStore (Ljava/util/List;)Ljava/security/KeyStore;
|
|
||||||
public final fun newKeystore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/List;)V
|
|
||||||
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
public static synthetic fun newPrivateKeyCertificatePair$default (Lapp/revanced/lib/ApkSigner;Ljava/lang/String;Ljava/util/Date;ILjava/lang/Object;)Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
|
|
||||||
public final fun signApk (Lcom/android/apksig/ApkSigner$Builder;Ljava/io/File;Ljava/io/File;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/ApkSigner$KeyStoreEntry {
|
|
||||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;)V
|
|
||||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getAlias ()Ljava/lang/String;
|
|
||||||
public final fun getPassword ()Ljava/lang/String;
|
|
||||||
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/lib/ApkSigner$PrivateKeyCertificatePair;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/ApkSigner$PrivateKeyCertificatePair {
|
|
||||||
public fun <init> (Ljava/security/PrivateKey;Ljava/security/cert/X509Certificate;)V
|
|
||||||
public final fun getCertificate ()Ljava/security/cert/X509Certificate;
|
|
||||||
public final fun getPrivateKey ()Ljava/security/PrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/ApkUtils {
|
|
||||||
public static final field INSTANCE Lapp/revanced/lib/ApkUtils;
|
|
||||||
public final fun copyAligned (Ljava/io/File;Ljava/io/File;Lapp/revanced/patcher/PatcherResult;)V
|
|
||||||
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/lib/ApkUtils$SigningOptions;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/ApkUtils$SigningOptions {
|
|
||||||
public fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
|
|
||||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getAlias ()Ljava/lang/String;
|
|
||||||
public final fun getKeyStore ()Ljava/io/File;
|
|
||||||
public final fun getKeyStorePassword ()Ljava/lang/String;
|
|
||||||
public final fun getPassword ()Ljava/lang/String;
|
|
||||||
public final fun getSigner ()Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/Options {
|
|
||||||
public static final field INSTANCE Lapp/revanced/lib/Options;
|
|
||||||
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/lib/Options$Patch;
|
|
||||||
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
|
|
||||||
public static synthetic fun serialize$default (Lapp/revanced/lib/Options;Ljava/util/Set;ZILjava/lang/Object;)Ljava/lang/String;
|
|
||||||
public final fun setOptions (Ljava/util/Set;Ljava/io/File;)V
|
|
||||||
public final fun setOptions (Ljava/util/Set;Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/Options$Patch {
|
|
||||||
public final fun getOptions ()Ljava/util/List;
|
|
||||||
public final fun getPatchName ()Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/Options$Patch$Option {
|
|
||||||
public final fun getKey ()Ljava/lang/String;
|
|
||||||
public final fun getValue ()Ljava/lang/Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/PatchUtils {
|
|
||||||
public static final field INSTANCE Lapp/revanced/lib/PatchUtils;
|
|
||||||
public final fun getMostCommonCompatibleVersion (Ljava/util/Set;Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class app/revanced/lib/adb/AdbManager {
|
|
||||||
public static final field Companion Lapp/revanced/lib/adb/AdbManager$Companion;
|
|
||||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
protected final fun getDevice ()Lse/vidstige/jadb/JadbDevice;
|
|
||||||
protected final fun getLogger ()Ljava/util/logging/Logger;
|
|
||||||
public fun install (Lapp/revanced/lib/adb/AdbManager$Apk;)V
|
|
||||||
public fun uninstall (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$Apk {
|
|
||||||
public fun <init> (Ljava/io/File;Ljava/lang/String;)V
|
|
||||||
public synthetic fun <init> (Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
|
||||||
public final fun getFile ()Ljava/io/File;
|
|
||||||
public final fun getPackageName ()Ljava/lang/String;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$Companion {
|
|
||||||
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/lib/adb/AdbManager;
|
|
||||||
public static synthetic fun getAdbManager$default (Lapp/revanced/lib/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/lib/adb/AdbManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$RootAdbManager : app/revanced/lib/adb/AdbManager {
|
|
||||||
public static final field Utils Lapp/revanced/lib/adb/AdbManager$RootAdbManager$Utils;
|
|
||||||
public fun install (Lapp/revanced/lib/adb/AdbManager$Apk;)V
|
|
||||||
public fun uninstall (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$RootAdbManager$Utils {
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/adb/AdbManager$UserAdbManager : app/revanced/lib/adb/AdbManager {
|
|
||||||
public fun install (Lapp/revanced/lib/adb/AdbManager$Apk;)V
|
|
||||||
public fun uninstall (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/logging/Logger {
|
|
||||||
public static final field INSTANCE Lapp/revanced/lib/logging/Logger;
|
|
||||||
public final fun addHandler (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V
|
|
||||||
public final fun removeAllHandlers ()V
|
|
||||||
public final fun setDefault ()V
|
|
||||||
public final fun setFormat (Ljava/lang/String;)V
|
|
||||||
public static synthetic fun setFormat$default (Lapp/revanced/lib/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/zip/ZipFile : java/io/Closeable {
|
|
||||||
public static final field ApkZipFile Lapp/revanced/lib/zip/ZipFile$ApkZipFile;
|
|
||||||
public fun <init> (Ljava/io/File;)V
|
|
||||||
public final fun addEntryCompressData (Lapp/revanced/lib/zip/structures/ZipEntry;[B)V
|
|
||||||
public fun close ()V
|
|
||||||
public final fun copyEntriesFromFileAligned (Lapp/revanced/lib/zip/ZipFile;Lkotlin/jvm/functions/Function1;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/zip/ZipFile$ApkZipFile {
|
|
||||||
public final fun getApkZipEntryAlignment ()Lkotlin/jvm/functions/Function1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/zip/structures/ZipEntry {
|
|
||||||
public static final field Companion Lapp/revanced/lib/zip/structures/ZipEntry$Companion;
|
|
||||||
public fun <init> (Ljava/lang/String;)V
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class app/revanced/lib/zip/structures/ZipEntry$Companion {
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("jvm") version "1.9.0"
|
|
||||||
alias(libs.plugins.binary.compatibility.validator)
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.revanced.patcher)
|
|
||||||
implementation(libs.kotlin.reflect)
|
|
||||||
implementation(libs.jadb) // Updated fork
|
|
||||||
implementation(libs.apksig)
|
|
||||||
implementation(libs.bcpkix.jdk18on)
|
|
||||||
implementation(libs.jackson.module.kotlin)
|
|
||||||
|
|
||||||
testImplementation(libs.revanced.patcher)
|
|
||||||
testImplementation(libs.kotlin.test)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
testLogging {
|
|
||||||
events("PASSED", "SKIPPED", "FAILED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin { jvmToolchain(11) }
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
maven {
|
|
||||||
name = "GitHubPackages"
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/revanced-cli")
|
|
||||||
credentials {
|
|
||||||
username = System.getenv("GITHUB_ACTOR")
|
|
||||||
password = System.getenv("GITHUB_TOKEN")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
publications {
|
|
||||||
create<MavenPublication>("gpr") {
|
|
||||||
from(components["java"])
|
|
||||||
|
|
||||||
version = project.version.toString()
|
|
||||||
|
|
||||||
pom {
|
|
||||||
name = "ReVanced Library"
|
|
||||||
description = "Library containing common utilities for ReVanced"
|
|
||||||
url = "https://revanced.app"
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
license {
|
|
||||||
name = "GNU General Public License v3.0"
|
|
||||||
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
developers {
|
|
||||||
developer {
|
|
||||||
id = "ReVanced"
|
|
||||||
name = "ReVanced"
|
|
||||||
email = "contact@revanced.app"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scm {
|
|
||||||
connection = "scm:git:git://github.com/revanced/revanced-cli.git"
|
|
||||||
developerConnection = "scm:git:git@github.com:revanced/revanced-cli.git"
|
|
||||||
url = "https://github.com/revanced/revanced-cli"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
rootProject.name = "revanced-lib"
|
|
@ -1,265 +0,0 @@
|
|||||||
package app.revanced.lib
|
|
||||||
|
|
||||||
import com.android.apksig.ApkSigner
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.*
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.*
|
|
||||||
import java.util.logging.Logger
|
|
||||||
import kotlin.time.Duration.Companion.days
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for writing or reading keystore files and entries as well as signing APK files.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
|
||||||
object ApkSigner {
|
|
||||||
private val logger = Logger.getLogger(app.revanced.lib.ApkSigner::class.java.name)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
|
|
||||||
Security.addProvider(BouncyCastleProvider())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [PrivateKeyCertificatePair].
|
|
||||||
*
|
|
||||||
* @param commonName The common name of the certificate.
|
|
||||||
* @param validUntil The date until the certificate is valid.
|
|
||||||
* @return The created [PrivateKeyCertificatePair].
|
|
||||||
*/
|
|
||||||
fun newPrivateKeyCertificatePair(
|
|
||||||
commonName: String = "ReVanced",
|
|
||||||
validUntil: Date = Date(System.currentTimeMillis() + 356.days.inWholeMilliseconds * 24)
|
|
||||||
): PrivateKeyCertificatePair {
|
|
||||||
logger.fine("Creating certificate for $commonName")
|
|
||||||
|
|
||||||
// Generate a new key pair.
|
|
||||||
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
|
|
||||||
initialize(2048)
|
|
||||||
}.generateKeyPair()
|
|
||||||
|
|
||||||
var serialNumber: BigInteger
|
|
||||||
do serialNumber = BigInteger.valueOf(SecureRandom().nextLong())
|
|
||||||
while (serialNumber < BigInteger.ZERO)
|
|
||||||
|
|
||||||
val name = X500Name("CN=$commonName")
|
|
||||||
|
|
||||||
// Create a new certificate.
|
|
||||||
val certificate = JcaX509CertificateConverter().getCertificate(
|
|
||||||
X509v3CertificateBuilder(
|
|
||||||
name,
|
|
||||||
serialNumber,
|
|
||||||
Date(System.currentTimeMillis()),
|
|
||||||
validUntil,
|
|
||||||
Locale.ENGLISH,
|
|
||||||
name,
|
|
||||||
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded)
|
|
||||||
).build(JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private))
|
|
||||||
)
|
|
||||||
|
|
||||||
return PrivateKeyCertificatePair(keyPair.private, certificate)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a [PrivateKeyCertificatePair] from a keystore entry.
|
|
||||||
*
|
|
||||||
* @param keyStore The keystore to read the entry from.
|
|
||||||
* @param keyStoreEntryAlias The alias of the key store entry to read.
|
|
||||||
* @param keyStoreEntryPassword The password for recovering the signing key.
|
|
||||||
* @return The read [PrivateKeyCertificatePair].
|
|
||||||
* @throws IllegalArgumentException If the keystore does not contain the given alias or the password is invalid.
|
|
||||||
*/
|
|
||||||
fun readKeyCertificatePair(
|
|
||||||
keyStore: KeyStore,
|
|
||||||
keyStoreEntryAlias: String,
|
|
||||||
keyStoreEntryPassword: String,
|
|
||||||
): PrivateKeyCertificatePair {
|
|
||||||
logger.fine("Reading key and certificate pair from keystore entry $keyStoreEntryAlias")
|
|
||||||
|
|
||||||
if (!keyStore.containsAlias(keyStoreEntryAlias))
|
|
||||||
throw IllegalArgumentException("Keystore does not contain alias $keyStoreEntryAlias")
|
|
||||||
|
|
||||||
// Read the private key and certificate from the keystore.
|
|
||||||
|
|
||||||
val privateKey = try {
|
|
||||||
keyStore.getKey(keyStoreEntryAlias, keyStoreEntryPassword.toCharArray()) as PrivateKey
|
|
||||||
} catch (exception: UnrecoverableKeyException) {
|
|
||||||
throw IllegalArgumentException("Invalid password for keystore entry $keyStoreEntryAlias")
|
|
||||||
}
|
|
||||||
|
|
||||||
val certificate = keyStore.getCertificate(keyStoreEntryAlias) as X509Certificate
|
|
||||||
|
|
||||||
return PrivateKeyCertificatePair(privateKey, certificate)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new keystore with a new keypair.
|
|
||||||
*
|
|
||||||
* @param entries The entries to add to the keystore.
|
|
||||||
* @return The created keystore.
|
|
||||||
* @see KeyStoreEntry
|
|
||||||
*/
|
|
||||||
fun newKeyStore(
|
|
||||||
entries: List<KeyStoreEntry>
|
|
||||||
): KeyStore {
|
|
||||||
logger.fine("Creating keystore")
|
|
||||||
|
|
||||||
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
|
|
||||||
entries.forEach { entry ->
|
|
||||||
load(null)
|
|
||||||
// Add all entries to the keystore.
|
|
||||||
setKeyEntry(
|
|
||||||
entry.alias,
|
|
||||||
entry.privateKeyCertificatePair.privateKey,
|
|
||||||
entry.password.toCharArray(),
|
|
||||||
arrayOf(entry.privateKeyCertificatePair.certificate)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new keystore with a new keypair and saves it to the given [keyStoreOutputStream].
|
|
||||||
*
|
|
||||||
* @param keyStoreOutputStream The stream to write the keystore to.
|
|
||||||
* @param keyStorePassword The password for the keystore.
|
|
||||||
* @param entries The entries to add to the keystore.
|
|
||||||
*/
|
|
||||||
fun newKeystore(
|
|
||||||
keyStoreOutputStream: OutputStream,
|
|
||||||
keyStorePassword: String,
|
|
||||||
entries: List<KeyStoreEntry>
|
|
||||||
) = newKeyStore(entries).store(
|
|
||||||
keyStoreOutputStream,
|
|
||||||
keyStorePassword.toCharArray()
|
|
||||||
) // Save the keystore.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a keystore from the given [keyStoreInputStream].
|
|
||||||
*
|
|
||||||
* @param keyStoreInputStream The stream to read the keystore from.
|
|
||||||
* @param keyStorePassword The password for the keystore.
|
|
||||||
* @return The keystore.
|
|
||||||
* @throws IllegalArgumentException If the keystore password is invalid.
|
|
||||||
*/
|
|
||||||
fun readKeyStore(
|
|
||||||
keyStoreInputStream: InputStream,
|
|
||||||
keyStorePassword: String?
|
|
||||||
): KeyStore {
|
|
||||||
logger.fine("Reading keystore")
|
|
||||||
|
|
||||||
return KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME).apply {
|
|
||||||
try {
|
|
||||||
load(keyStoreInputStream, keyStorePassword?.toCharArray())
|
|
||||||
} catch (exception: IOException) {
|
|
||||||
if (exception.cause is UnrecoverableKeyException)
|
|
||||||
throw IllegalArgumentException("Invalid keystore password")
|
|
||||||
else
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [ApkSigner.Builder].
|
|
||||||
*
|
|
||||||
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
|
|
||||||
* @param signer The name of the signer.
|
|
||||||
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
|
|
||||||
* @return The created [ApkSigner.Builder] instance.
|
|
||||||
*/
|
|
||||||
fun newApkSignerBuilder(
|
|
||||||
privateKeyCertificatePair: PrivateKeyCertificatePair,
|
|
||||||
signer: String,
|
|
||||||
createdBy: String
|
|
||||||
): ApkSigner.Builder {
|
|
||||||
logger.fine(
|
|
||||||
"Creating new ApkSigner " +
|
|
||||||
"with $signer as signer and " +
|
|
||||||
"$createdBy as Created-By attribute in the APK's manifest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create the signer config.
|
|
||||||
val signerConfig = ApkSigner.SignerConfig.Builder(
|
|
||||||
signer,
|
|
||||||
privateKeyCertificatePair.privateKey,
|
|
||||||
listOf(privateKeyCertificatePair.certificate)
|
|
||||||
).build()
|
|
||||||
|
|
||||||
// Create the signer.
|
|
||||||
return ApkSigner.Builder(listOf(signerConfig)).apply {
|
|
||||||
setCreatedBy(createdBy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new [ApkSigner.Builder].
|
|
||||||
*
|
|
||||||
* @param keyStore The keystore to use for signing.
|
|
||||||
* @param keyStoreEntryAlias The alias of the key store entry to use for signing.
|
|
||||||
* @param keyStoreEntryPassword The password for recovering the signing key.
|
|
||||||
* @param signer The name of the signer.
|
|
||||||
* @param createdBy The value for the `Created-By` attribute in the APK's manifest.
|
|
||||||
* @return The created [ApkSigner.Builder] instance.
|
|
||||||
* @see KeyStoreEntry
|
|
||||||
* @see PrivateKeyCertificatePair
|
|
||||||
* @see ApkSigner.Builder.setCreatedBy
|
|
||||||
* @see ApkSigner.Builder.signApk
|
|
||||||
*/
|
|
||||||
fun newApkSignerBuilder(
|
|
||||||
keyStore: KeyStore,
|
|
||||||
keyStoreEntryAlias: String,
|
|
||||||
keyStoreEntryPassword: String,
|
|
||||||
signer: String,
|
|
||||||
createdBy: String,
|
|
||||||
) = newApkSignerBuilder(
|
|
||||||
readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword),
|
|
||||||
signer,
|
|
||||||
createdBy
|
|
||||||
)
|
|
||||||
|
|
||||||
fun ApkSigner.Builder.signApk(input: File, output: File) {
|
|
||||||
logger.info("Signing ${input.name}")
|
|
||||||
|
|
||||||
setInputApk(input)
|
|
||||||
setOutputApk(output)
|
|
||||||
|
|
||||||
build().sign()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An entry in a keystore.
|
|
||||||
*
|
|
||||||
* @param alias The alias of the entry.
|
|
||||||
* @param password The password for recovering the signing key.
|
|
||||||
* @param privateKeyCertificatePair The private key and certificate pair.
|
|
||||||
* @see PrivateKeyCertificatePair
|
|
||||||
*/
|
|
||||||
class KeyStoreEntry(
|
|
||||||
val alias: String,
|
|
||||||
val password: String,
|
|
||||||
val privateKeyCertificatePair: PrivateKeyCertificatePair = newPrivateKeyCertificatePair()
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A private key and certificate pair.
|
|
||||||
*
|
|
||||||
* @param privateKey The private key.
|
|
||||||
* @param certificate The certificate.
|
|
||||||
*/
|
|
||||||
class PrivateKeyCertificatePair(
|
|
||||||
val privateKey: PrivateKey,
|
|
||||||
val certificate: X509Certificate,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
package app.revanced.lib
|
|
||||||
|
|
||||||
import app.revanced.lib.ApkSigner.signApk
|
|
||||||
import app.revanced.lib.zip.ZipFile
|
|
||||||
import app.revanced.lib.zip.structures.ZipEntry
|
|
||||||
import app.revanced.patcher.PatcherResult
|
|
||||||
import java.io.File
|
|
||||||
import java.util.logging.Logger
|
|
||||||
import kotlin.io.path.deleteIfExists
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility functions for working with apks.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
|
||||||
object ApkUtils {
|
|
||||||
private val logger = Logger.getLogger(ApkUtils::class.java.name)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new apk from [apkFile] and [patchedEntriesSource] and writes it to [outputFile].
|
|
||||||
*
|
|
||||||
* @param apkFile The apk to copy entries from.
|
|
||||||
* @param outputFile The apk to write the new entries to.
|
|
||||||
* @param patchedEntriesSource The result of the patcher to add the patched dex files and resources.
|
|
||||||
*/
|
|
||||||
fun copyAligned(apkFile: File, outputFile: File, patchedEntriesSource: PatcherResult) {
|
|
||||||
logger.info("Aligning ${apkFile.name}")
|
|
||||||
|
|
||||||
outputFile.toPath().deleteIfExists()
|
|
||||||
|
|
||||||
ZipFile(outputFile).use { file ->
|
|
||||||
patchedEntriesSource.dexFiles.forEach {
|
|
||||||
file.addEntryCompressData(
|
|
||||||
ZipEntry(it.name), it.stream.readBytes()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
patchedEntriesSource.resourceFile?.let {
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(it), ZipFile.apkZipEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Do not compress result.doNotCompress
|
|
||||||
|
|
||||||
// TODO: Fix copying resources that are not needed anymore.
|
|
||||||
file.copyEntriesFromFileAligned(
|
|
||||||
ZipFile(apkFile), ZipFile.apkZipEntryAlignment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the [apk] file and writes it to [output].
|
|
||||||
*
|
|
||||||
* @param apk The apk to sign.
|
|
||||||
* @param output The apk to write the signed apk to.
|
|
||||||
* @param signingOptions The options to use for signing.
|
|
||||||
*/
|
|
||||||
fun sign(
|
|
||||||
apk: File,
|
|
||||||
output: File,
|
|
||||||
signingOptions: SigningOptions,
|
|
||||||
) {
|
|
||||||
// Get the keystore from the file or create a new one.
|
|
||||||
val keyStore = if (signingOptions.keyStore.exists()) {
|
|
||||||
ApkSigner.readKeyStore(signingOptions.keyStore.inputStream(), signingOptions.keyStorePassword)
|
|
||||||
} else {
|
|
||||||
val entry = ApkSigner.KeyStoreEntry(signingOptions.alias, signingOptions.password)
|
|
||||||
|
|
||||||
// Create a new keystore with a new keypair and saves it.
|
|
||||||
ApkSigner.newKeyStore(listOf(entry)).also { keyStore ->
|
|
||||||
keyStore.store(
|
|
||||||
signingOptions.keyStore.outputStream(),
|
|
||||||
signingOptions.keyStorePassword?.toCharArray()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApkSigner.newApkSignerBuilder(
|
|
||||||
keyStore,
|
|
||||||
signingOptions.alias,
|
|
||||||
signingOptions.password,
|
|
||||||
signingOptions.signer,
|
|
||||||
signingOptions.signer
|
|
||||||
).signApk(apk, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for signing an apk.
|
|
||||||
*
|
|
||||||
* @param keyStore The keystore to use for signing.
|
|
||||||
* @param keyStorePassword The password for the keystore.
|
|
||||||
* @param alias The alias of the key store entry to use for signing.
|
|
||||||
* @param password The password for recovering the signing key.
|
|
||||||
* @param signer The name of the signer.
|
|
||||||
*/
|
|
||||||
class SigningOptions(
|
|
||||||
val keyStore: File,
|
|
||||||
val keyStorePassword: String?,
|
|
||||||
val alias: String = "ReVanced Key",
|
|
||||||
val password: String = "",
|
|
||||||
val signer: String = "ReVanced",
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,107 +0,0 @@
|
|||||||
@file:Suppress("MemberVisibilityCanBePrivate")
|
|
||||||
|
|
||||||
package app.revanced.lib
|
|
||||||
|
|
||||||
|
|
||||||
import app.revanced.lib.Options.Patch.Option
|
|
||||||
import app.revanced.patcher.PatchClass
|
|
||||||
import app.revanced.patcher.PatchSet
|
|
||||||
import app.revanced.patcher.patch.options.PatchOptionException
|
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|
||||||
import java.io.File
|
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
private typealias PatchList = List<PatchClass>
|
|
||||||
|
|
||||||
object Options {
|
|
||||||
private val logger = Logger.getLogger(Options::class.java.name)
|
|
||||||
|
|
||||||
private var mapper = jacksonObjectMapper()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param patches The list of patches to serialize.
|
|
||||||
* @param prettyPrint Whether to pretty print the JSON.
|
|
||||||
* @return The JSON string containing the options.
|
|
||||||
*/
|
|
||||||
fun serialize(patches: PatchSet, prettyPrint: Boolean = false): String = patches
|
|
||||||
.filter { it.options.any() }
|
|
||||||
.map { patch ->
|
|
||||||
Patch(
|
|
||||||
patch.name!!,
|
|
||||||
patch.options.values.map { option -> Option(option.key, option.value) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// See https://github.com/revanced/revanced-patches/pull/2434/commits/60e550550b7641705e81aa72acfc4faaebb225e7.
|
|
||||||
.distinctBy { it.patchName }
|
|
||||||
.let {
|
|
||||||
if (prettyPrint)
|
|
||||||
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(it)
|
|
||||||
else
|
|
||||||
mapper.writeValueAsString(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deserializes the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param json The JSON string containing the options.
|
|
||||||
* @return The list of [Patch]s.
|
|
||||||
* @see Patch
|
|
||||||
* @see PatchList
|
|
||||||
*/
|
|
||||||
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param json The JSON string containing the options.
|
|
||||||
*/
|
|
||||||
fun PatchSet.setOptions(json: String) {
|
|
||||||
filter { it.options.any() }.let { patches ->
|
|
||||||
if (patches.isEmpty()) return
|
|
||||||
|
|
||||||
val patchOptions = deserialize(json)
|
|
||||||
|
|
||||||
patches.forEach patch@{ patch ->
|
|
||||||
patchOptions.find { option -> option.patchName == patch.name!! }?.let {
|
|
||||||
it.options.forEach { option ->
|
|
||||||
try {
|
|
||||||
patch.options[option.key] = option.value
|
|
||||||
} catch (e: PatchOptionException) {
|
|
||||||
logger.severe(e.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the options for the patches in the list.
|
|
||||||
*
|
|
||||||
* @param file The file containing the JSON string containing the options.
|
|
||||||
* @see setOptions
|
|
||||||
*/
|
|
||||||
fun PatchSet.setOptions(file: File) = setOptions(file.readText())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data class for a patch and its [Option]s.
|
|
||||||
*
|
|
||||||
* @property patchName The name of the patch.
|
|
||||||
* @property options The [Option]s for the patch.
|
|
||||||
*/
|
|
||||||
class Patch internal constructor(
|
|
||||||
val patchName: String,
|
|
||||||
val options: List<Option>
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data class for patch option.
|
|
||||||
*
|
|
||||||
* @property key The name of the option.
|
|
||||||
* @property value The value of the option.
|
|
||||||
*/
|
|
||||||
class Option internal constructor(val key: String, val value: Any?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package app.revanced.lib
|
|
||||||
|
|
||||||
import app.revanced.patcher.PatchSet
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility functions for working with patches.
|
|
||||||
*/
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
|
||||||
object PatchUtils {
|
|
||||||
/**
|
|
||||||
* Get the version that is most common for [packageName] in the supplied set of [patches].
|
|
||||||
*
|
|
||||||
* @param patches The set of patches to check.
|
|
||||||
* @param packageName The name of the compatible package.
|
|
||||||
* @return The most common version of.
|
|
||||||
*/
|
|
||||||
fun getMostCommonCompatibleVersion(patches: PatchSet, packageName: String) = patches
|
|
||||||
.mapNotNull {
|
|
||||||
// Map all patches to their compatible packages with version constraints.
|
|
||||||
it.compatiblePackages?.firstOrNull { compatiblePackage ->
|
|
||||||
compatiblePackage.name == packageName && compatiblePackage.versions?.isNotEmpty() == true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flatMap { it.versions!! }
|
|
||||||
.groupingBy { it }
|
|
||||||
.eachCount()
|
|
||||||
.maxByOrNull { it.value }?.key
|
|
||||||
}
|
|
@ -1,154 +0,0 @@
|
|||||||
package app.revanced.lib.adb
|
|
||||||
|
|
||||||
import app.revanced.lib.adb.AdbManager.Apk
|
|
||||||
import app.revanced.lib.adb.Constants.CREATE_DIR
|
|
||||||
import app.revanced.lib.adb.Constants.DELETE
|
|
||||||
import app.revanced.lib.adb.Constants.INSTALLATION_PATH
|
|
||||||
import app.revanced.lib.adb.Constants.INSTALL_MOUNT
|
|
||||||
import app.revanced.lib.adb.Constants.INSTALL_PATCHED_APK
|
|
||||||
import app.revanced.lib.adb.Constants.MOUNT_PATH
|
|
||||||
import app.revanced.lib.adb.Constants.MOUNT_SCRIPT
|
|
||||||
import app.revanced.lib.adb.Constants.PATCHED_APK_PATH
|
|
||||||
import app.revanced.lib.adb.Constants.PLACEHOLDER
|
|
||||||
import app.revanced.lib.adb.Constants.RESOLVE_ACTIVITY
|
|
||||||
import app.revanced.lib.adb.Constants.RESTART
|
|
||||||
import app.revanced.lib.adb.Constants.TMP_PATH
|
|
||||||
import app.revanced.lib.adb.Constants.UMOUNT
|
|
||||||
import se.vidstige.jadb.JadbConnection
|
|
||||||
import se.vidstige.jadb.JadbDevice
|
|
||||||
import se.vidstige.jadb.managers.Package
|
|
||||||
import se.vidstige.jadb.managers.PackageManager
|
|
||||||
import java.io.File
|
|
||||||
import java.util.logging.Logger
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adb manager. Used to install and uninstall [Apk] files.
|
|
||||||
*
|
|
||||||
* @param deviceSerial The serial of the device.
|
|
||||||
*/
|
|
||||||
sealed class AdbManager private constructor(deviceSerial: String? = null) {
|
|
||||||
protected val logger: Logger = Logger.getLogger(AdbManager::class.java.name)
|
|
||||||
|
|
||||||
protected val device = JadbConnection().devices.find { device -> device.serial == deviceSerial }
|
|
||||||
?: throw DeviceNotFoundException(deviceSerial)
|
|
||||||
|
|
||||||
init {
|
|
||||||
logger.fine("Established connection to $deviceSerial")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs the [Apk] file.
|
|
||||||
*
|
|
||||||
* @param apk The [Apk] file.
|
|
||||||
*/
|
|
||||||
open fun install(apk: Apk) {
|
|
||||||
logger.info("Finished installing ${apk.file.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstalls the package.
|
|
||||||
*
|
|
||||||
* @param packageName The package name.
|
|
||||||
*/
|
|
||||||
open fun uninstall(packageName: String) {
|
|
||||||
logger.info("Finished uninstalling $packageName")
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Gets an [AdbManager] for the supplied device serial.
|
|
||||||
*
|
|
||||||
* @param deviceSerial The device serial.
|
|
||||||
* @param root Whether to use root or not.
|
|
||||||
* @return The [AdbManager].
|
|
||||||
* @throws DeviceNotFoundException If the device can not be found.
|
|
||||||
*/
|
|
||||||
fun getAdbManager(deviceSerial: String, root: Boolean = false): AdbManager =
|
|
||||||
if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
|
|
||||||
}
|
|
||||||
|
|
||||||
class RootAdbManager internal constructor(deviceSerial: String) : AdbManager(deviceSerial) {
|
|
||||||
init {
|
|
||||||
if (!device.hasSu()) throw IllegalArgumentException("Root required on $deviceSerial. Task failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
logger.info("Installing by mounting")
|
|
||||||
|
|
||||||
val packageName = apk.packageName ?: throw PackageNameRequiredException()
|
|
||||||
|
|
||||||
device.run(RESOLVE_ACTIVITY, packageName).inputStream.bufferedReader().readLine().let { line ->
|
|
||||||
if (line != "No activity found") return@let
|
|
||||||
throw throw FailedToFindInstalledPackageException(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
device.push(apk.file, TMP_PATH)
|
|
||||||
|
|
||||||
device.run("$CREATE_DIR $INSTALLATION_PATH")
|
|
||||||
device.run(INSTALL_PATCHED_APK, packageName)
|
|
||||||
|
|
||||||
device.createFile(TMP_PATH, MOUNT_SCRIPT.applyReplacement(packageName))
|
|
||||||
|
|
||||||
device.run(INSTALL_MOUNT, packageName)
|
|
||||||
device.run(UMOUNT, packageName) // Sanity check.
|
|
||||||
device.run(MOUNT_PATH, packageName)
|
|
||||||
device.run(RESTART, packageName)
|
|
||||||
device.run(DELETE, TMP_PATH)
|
|
||||||
|
|
||||||
super.install(apk)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
|
||||||
logger.info("Uninstalling $packageName by unmounting")
|
|
||||||
|
|
||||||
device.run(UMOUNT, packageName)
|
|
||||||
device.run(DELETE.applyReplacement(PATCHED_APK_PATH), packageName)
|
|
||||||
device.run(DELETE, MOUNT_PATH)
|
|
||||||
device.run(DELETE, TMP_PATH)
|
|
||||||
|
|
||||||
super.uninstall(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object Utils {
|
|
||||||
private fun JadbDevice.run(command: String, with: String) = run(command.applyReplacement(with))
|
|
||||||
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserAdbManager internal constructor(deviceSerial: String) : AdbManager(deviceSerial) {
|
|
||||||
private val packageManager = PackageManager(device)
|
|
||||||
|
|
||||||
override fun install(apk: Apk) {
|
|
||||||
PackageManager(device).install(apk.file)
|
|
||||||
|
|
||||||
super.install(apk)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun uninstall(packageName: String) {
|
|
||||||
logger.info("Uninstalling $packageName")
|
|
||||||
|
|
||||||
packageManager.uninstall(Package(packageName))
|
|
||||||
|
|
||||||
super.uninstall(packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apk file for [AdbManager].
|
|
||||||
*
|
|
||||||
* @param file The [Apk] file.
|
|
||||||
* @param packageName The package name of the [Apk] file.
|
|
||||||
*/
|
|
||||||
class Apk(val file: File, val packageName: String? = null)
|
|
||||||
|
|
||||||
class DeviceNotFoundException internal constructor(deviceSerial: String?) :
|
|
||||||
Exception(deviceSerial?.let {
|
|
||||||
"The device with the ADB device serial \"$deviceSerial\" can not be found"
|
|
||||||
} ?: "No ADB device found")
|
|
||||||
|
|
||||||
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
|
|
||||||
Exception("Failed to find installed package \"$packageName\" because no activity was found")
|
|
||||||
|
|
||||||
class PackageNameRequiredException internal constructor() :
|
|
||||||
Exception("Package name is required")
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package app.revanced.lib.adb
|
|
||||||
|
|
||||||
import se.vidstige.jadb.JadbDevice
|
|
||||||
import se.vidstige.jadb.RemoteFile
|
|
||||||
import se.vidstige.jadb.ShellProcess
|
|
||||||
import se.vidstige.jadb.ShellProcessBuilder
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
|
|
||||||
internal fun JadbDevice.buildCommand(command: String, su: Boolean = true): ShellProcessBuilder {
|
|
||||||
if (su) return shellProcessBuilder("su -c \'$command\'")
|
|
||||||
|
|
||||||
val args = command.split(" ") as ArrayList<String>
|
|
||||||
val cmd = args.removeFirst()
|
|
||||||
|
|
||||||
return shellProcessBuilder(cmd, *args.toTypedArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.run(command: String, su: Boolean = true): ShellProcess {
|
|
||||||
return this.buildCommand(command, su).start()!!
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun JadbDevice.hasSu() =
|
|
||||||
this.startCommand("su -h", false).waitFor() == 0
|
|
||||||
|
|
||||||
internal fun JadbDevice.push(file: File, targetFilePath: String) =
|
|
||||||
push(file, RemoteFile(targetFilePath))
|
|
||||||
|
|
||||||
internal fun JadbDevice.createFile(targetFile: String, content: String) =
|
|
||||||
push(content.byteInputStream(), System.currentTimeMillis(), 644, RemoteFile(targetFile))
|
|
||||||
|
|
||||||
|
|
||||||
private fun JadbDevice.startCommand(command: String, su: Boolean) =
|
|
||||||
shellProcessBuilder(if (su) "su -c '$command'" else command).start()
|
|
@ -1,41 +0,0 @@
|
|||||||
package app.revanced.lib.adb
|
|
||||||
|
|
||||||
internal object Constants {
|
|
||||||
internal const val PLACEHOLDER = "PLACEHOLDER"
|
|
||||||
|
|
||||||
internal const val TMP_PATH = "/data/local/tmp/revanced.tmp"
|
|
||||||
internal const val INSTALLATION_PATH = "/data/adb/revanced/"
|
|
||||||
internal const val PATCHED_APK_PATH = "$INSTALLATION_PATH$PLACEHOLDER.apk"
|
|
||||||
internal const val MOUNT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
|
|
||||||
|
|
||||||
internal const val DELETE = "rm -rf $PLACEHOLDER"
|
|
||||||
internal const val CREATE_DIR = "mkdir -p"
|
|
||||||
internal const val RESOLVE_ACTIVITY = "pm resolve-activity --brief $PLACEHOLDER"
|
|
||||||
internal const val RESTART = "$RESOLVE_ACTIVITY | tail -n 1 | " +
|
|
||||||
"xargs am start -n && kill ${'$'}(pidof -s $PLACEHOLDER)"
|
|
||||||
|
|
||||||
internal const val INSTALL_PATCHED_APK = "base_path=\"$PATCHED_APK_PATH\" && " +
|
|
||||||
"mv $TMP_PATH ${'$'}base_path && " +
|
|
||||||
"chmod 644 ${'$'}base_path && " +
|
|
||||||
"chown system:system ${'$'}base_path && " +
|
|
||||||
"chcon u:object_r:apk_data_file:s0 ${'$'}base_path"
|
|
||||||
|
|
||||||
internal const val UMOUNT =
|
|
||||||
"grep $PLACEHOLDER /proc/mounts | while read -r line; do echo ${'$'}line | cut -d \" \" -f 2 | sed 's/apk.*/apk/' | xargs -r umount -l; done"
|
|
||||||
|
|
||||||
internal const val INSTALL_MOUNT = "mv $TMP_PATH $MOUNT_PATH && chmod +x $MOUNT_PATH"
|
|
||||||
|
|
||||||
internal val MOUNT_SCRIPT =
|
|
||||||
"""
|
|
||||||
#!/system/bin/sh
|
|
||||||
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
|
|
||||||
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
|
||||||
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
|
||||||
|
|
||||||
base_path="$PATCHED_APK_PATH"
|
|
||||||
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
|
||||||
|
|
||||||
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
|
||||||
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package app.revanced.lib.logging
|
|
||||||
|
|
||||||
import java.util.logging.Handler
|
|
||||||
import java.util.logging.Level
|
|
||||||
import java.util.logging.LogRecord
|
|
||||||
import java.util.logging.SimpleFormatter
|
|
||||||
|
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
|
||||||
object Logger {
|
|
||||||
private val rootLogger = java.util.logging.Logger.getLogger("")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the format for the logger.
|
|
||||||
*
|
|
||||||
* @param format The format to use.
|
|
||||||
*/
|
|
||||||
fun setFormat(format: String = "%4\$s: %5\$s %n") {
|
|
||||||
System.setProperty("java.util.logging.SimpleFormatter.format", format)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all handlers from the logger.
|
|
||||||
*/
|
|
||||||
fun removeAllHandlers() {
|
|
||||||
rootLogger.let { logger ->
|
|
||||||
logger.handlers.forEach { handler ->
|
|
||||||
handler.close()
|
|
||||||
logger.removeHandler(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a handler to the logger.
|
|
||||||
*
|
|
||||||
* @param publishHandler The handler for publishing the log.
|
|
||||||
* @param flushHandler The handler for flushing the log.
|
|
||||||
* @param closeHandler The handler for closing the log.
|
|
||||||
*/
|
|
||||||
fun addHandler(
|
|
||||||
publishHandler: (log: String, level: Level, loggerName: String?) -> Unit,
|
|
||||||
flushHandler: () -> Unit,
|
|
||||||
closeHandler: () -> Unit
|
|
||||||
) = object : Handler() {
|
|
||||||
override fun publish(record: LogRecord) = publishHandler(
|
|
||||||
formatter.format(record),
|
|
||||||
record.level,
|
|
||||||
record.loggerName
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun flush() = flushHandler()
|
|
||||||
|
|
||||||
override fun close() = closeHandler()
|
|
||||||
}.also {
|
|
||||||
it.level = Level.ALL
|
|
||||||
it.formatter = SimpleFormatter()
|
|
||||||
}.let(rootLogger::addHandler)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log to "standard" (error) output streams.
|
|
||||||
*/
|
|
||||||
fun setDefault() {
|
|
||||||
setFormat()
|
|
||||||
removeAllHandlers()
|
|
||||||
|
|
||||||
val publishHandler = handler@{ log: String, level: Level, loggerName: String? ->
|
|
||||||
if (loggerName?.startsWith("app.revanced") != true) return@handler
|
|
||||||
|
|
||||||
log.toByteArray().let {
|
|
||||||
if (level.intValue() > Level.WARNING.intValue())
|
|
||||||
System.err.write(it)
|
|
||||||
else
|
|
||||||
System.out.write(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val flushHandler = {
|
|
||||||
System.out.flush()
|
|
||||||
System.err.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
addHandler(publishHandler, flushHandler, flushHandler)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package app.revanced.lib.zip
|
|
||||||
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.io.DataOutput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
internal fun UInt.toLittleEndian() =
|
|
||||||
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
|
||||||
|
|
||||||
internal fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
|
||||||
|
|
||||||
internal fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
|
||||||
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
|
||||||
|
|
||||||
internal fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
|
||||||
|
|
||||||
internal fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
|
||||||
internal fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
|
||||||
|
|
||||||
internal fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
|
||||||
internal fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
|
||||||
|
|
||||||
internal fun DataInput.readUShort() = this.readShort().toUShort()
|
|
||||||
internal fun DataInput.readUInt() = this.readInt().toUInt()
|
|
||||||
|
|
||||||
internal fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
|
||||||
internal fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
|
||||||
|
|
||||||
internal fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
|
||||||
internal fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
|
||||||
|
|
||||||
internal fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
|
||||||
internal fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
|
@ -1,192 +0,0 @@
|
|||||||
package app.revanced.lib.zip
|
|
||||||
|
|
||||||
import app.revanced.lib.zip.structures.ZipEndRecord
|
|
||||||
import app.revanced.lib.zip.structures.ZipEntry
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.io.RandomAccessFile
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import java.util.zip.CRC32
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
|
|
||||||
class ZipFile(file: File) : Closeable {
|
|
||||||
private var entries: MutableList<ZipEntry> = mutableListOf()
|
|
||||||
|
|
||||||
private val filePointer: RandomAccessFile = RandomAccessFile(file, if (file.canWrite()) "rw" else "r")
|
|
||||||
private var centralDirectoryNeedsRewrite = false
|
|
||||||
|
|
||||||
private val compressionLevel = 5
|
|
||||||
|
|
||||||
init {
|
|
||||||
// If file isn't empty try to load entries.
|
|
||||||
if (file.length() > 0) {
|
|
||||||
val endRecord = findEndRecord()
|
|
||||||
|
|
||||||
if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
|
|
||||||
throw IllegalArgumentException("Multi-file archives are not supported")
|
|
||||||
|
|
||||||
entries = readEntries(endRecord).toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek back to start for writing.
|
|
||||||
filePointer.seek(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findEndRecord(): ZipEndRecord {
|
|
||||||
// Look from end to start since end record is at the end.
|
|
||||||
for (i in filePointer.length() - 1 downTo 0) {
|
|
||||||
filePointer.seek(i)
|
|
||||||
// Possible beginning of signature.
|
|
||||||
if (filePointer.readByte() == 0x50.toByte()) {
|
|
||||||
// Seek back to get the full int.
|
|
||||||
filePointer.seek(i)
|
|
||||||
val possibleSignature = filePointer.readUIntLE()
|
|
||||||
if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
|
|
||||||
filePointer.seek(i)
|
|
||||||
return ZipEndRecord.fromECD(filePointer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Exception("Couldn't find end record")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readEntries(endRecord: ZipEndRecord): List<ZipEntry> {
|
|
||||||
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
|
|
||||||
|
|
||||||
val numberOfEntries = endRecord.diskEntries.toInt()
|
|
||||||
|
|
||||||
return buildList(numberOfEntries) {
|
|
||||||
for (i in 1..numberOfEntries) {
|
|
||||||
add(
|
|
||||||
ZipEntry.fromCDE(filePointer).also
|
|
||||||
{
|
|
||||||
//for some reason the local extra field can be different from the central one
|
|
||||||
it.readLocalExtra(
|
|
||||||
filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
it.localHeaderOffset.toLong() + 28,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeCD() {
|
|
||||||
val centralDirectoryStartOffset = filePointer.channel.position().toUInt()
|
|
||||||
|
|
||||||
entries.forEach {
|
|
||||||
filePointer.channel.write(it.toCDE())
|
|
||||||
}
|
|
||||||
|
|
||||||
val entriesCount = entries.size.toUShort()
|
|
||||||
|
|
||||||
val endRecord = ZipEndRecord(
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
entriesCount,
|
|
||||||
entriesCount,
|
|
||||||
filePointer.channel.position().toUInt() - centralDirectoryStartOffset,
|
|
||||||
centralDirectoryStartOffset,
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
filePointer.channel.write(endRecord.toECD())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
|
||||||
centralDirectoryNeedsRewrite = true
|
|
||||||
|
|
||||||
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
|
||||||
|
|
||||||
filePointer.channel.write(entry.toLFH())
|
|
||||||
filePointer.channel.write(data)
|
|
||||||
|
|
||||||
entries.add(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
|
||||||
val compressor = Deflater(compressionLevel, true)
|
|
||||||
compressor.setInput(data)
|
|
||||||
compressor.finish()
|
|
||||||
|
|
||||||
val uncompressedSize = data.size
|
|
||||||
val compressedData = ByteArray(uncompressedSize) // I'm guessing compression won't make the data bigger.
|
|
||||||
|
|
||||||
val compressedDataLength = compressor.deflate(compressedData)
|
|
||||||
val compressedBuffer =
|
|
||||||
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
|
|
||||||
|
|
||||||
compressor.end()
|
|
||||||
|
|
||||||
val crc = CRC32()
|
|
||||||
crc.update(data)
|
|
||||||
|
|
||||||
entry.compression = 8u // Deflate compression.
|
|
||||||
entry.uncompressedSize = uncompressedSize.toUInt()
|
|
||||||
entry.compressedSize = compressedDataLength.toUInt()
|
|
||||||
entry.crc32 = crc.value.toUInt()
|
|
||||||
|
|
||||||
addEntry(entry, compressedBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
|
|
||||||
alignment?.let {
|
|
||||||
// Calculate where data would end up.
|
|
||||||
val dataOffset = filePointer.filePointer + entry.LFHSize
|
|
||||||
|
|
||||||
val mod = dataOffset % alignment
|
|
||||||
|
|
||||||
// Wrong alignment.
|
|
||||||
if (mod != 0L) {
|
|
||||||
// Add padding at end of extra field.
|
|
||||||
entry.localExtraField =
|
|
||||||
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addEntry(entry, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDataForEntry(entry: ZipEntry): ByteBuffer {
|
|
||||||
return filePointer.channel.map(
|
|
||||||
FileChannel.MapMode.READ_ONLY,
|
|
||||||
entry.dataOffset.toLong(),
|
|
||||||
entry.compressedSize.toLong()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies all entries from [file] to this file but skip already existing entries.
|
|
||||||
*
|
|
||||||
* @param file The file to copy entries from.
|
|
||||||
* @param entryAlignment A function that returns the alignment for a given entry.
|
|
||||||
*/
|
|
||||||
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
|
|
||||||
for (entry in file.entries) {
|
|
||||||
if (entries.any { it.fileName == entry.fileName }) continue // Skip duplicates
|
|
||||||
|
|
||||||
val data = file.getDataForEntry(entry)
|
|
||||||
addEntryCopyData(entry, data, entryAlignment(entry))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
if (centralDirectoryNeedsRewrite) writeCD()
|
|
||||||
filePointer.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object ApkZipFile {
|
|
||||||
private const val DEFAULT_ALIGNMENT = 4
|
|
||||||
private const val LIBRARY_ALIGNMENT = 4096
|
|
||||||
|
|
||||||
val apkZipEntryAlignment = { entry: ZipEntry ->
|
|
||||||
if (entry.compression.toUInt() != 0u) null
|
|
||||||
else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT
|
|
||||||
else DEFAULT_ALIGNMENT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package app.revanced.lib.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.lib.zip.putUInt
|
|
||||||
import app.revanced.lib.zip.putUShort
|
|
||||||
import app.revanced.lib.zip.readUIntLE
|
|
||||||
import app.revanced.lib.zip.readUShortLE
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
internal class ZipEndRecord(
|
|
||||||
val diskNumber: UShort,
|
|
||||||
val startingDiskNumber: UShort,
|
|
||||||
val diskEntries: UShort,
|
|
||||||
val totalEntries: UShort,
|
|
||||||
val centralDirectorySize: UInt,
|
|
||||||
val centralDirectoryStartOffset: UInt,
|
|
||||||
val fileComment: String,
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val ECD_HEADER_SIZE = 22
|
|
||||||
const val ECD_SIGNATURE = 0x06054b50u
|
|
||||||
|
|
||||||
fun fromECD(input: DataInput): ZipEndRecord {
|
|
||||||
val signature = input.readUIntLE()
|
|
||||||
|
|
||||||
if (signature != ECD_SIGNATURE)
|
|
||||||
throw IllegalArgumentException("Input doesn't start with end record signature")
|
|
||||||
|
|
||||||
val diskNumber = input.readUShortLE()
|
|
||||||
val startingDiskNumber = input.readUShortLE()
|
|
||||||
val diskEntries = input.readUShortLE()
|
|
||||||
val totalEntries = input.readUShortLE()
|
|
||||||
val centralDirectorySize = input.readUIntLE()
|
|
||||||
val centralDirectoryStartOffset = input.readUIntLE()
|
|
||||||
val fileCommentLength = input.readUShortLE()
|
|
||||||
var fileComment = ""
|
|
||||||
|
|
||||||
if (fileCommentLength > 0u) {
|
|
||||||
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
|
||||||
input.readFully(fileCommentBytes)
|
|
||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ZipEndRecord(
|
|
||||||
diskNumber,
|
|
||||||
startingDiskNumber,
|
|
||||||
diskEntries,
|
|
||||||
totalEntries,
|
|
||||||
centralDirectorySize,
|
|
||||||
centralDirectoryStartOffset,
|
|
||||||
fileComment
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toECD(): ByteBuffer {
|
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(ECD_SIGNATURE)
|
|
||||||
buffer.putUShort(diskNumber)
|
|
||||||
buffer.putUShort(startingDiskNumber)
|
|
||||||
buffer.putUShort(diskEntries)
|
|
||||||
buffer.putUShort(totalEntries)
|
|
||||||
buffer.putUInt(centralDirectorySize)
|
|
||||||
buffer.putUInt(centralDirectoryStartOffset)
|
|
||||||
buffer.putUShort(commentBytes.size.toUShort())
|
|
||||||
|
|
||||||
buffer.put(commentBytes)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
package app.revanced.lib.zip.structures
|
|
||||||
|
|
||||||
import app.revanced.lib.zip.*
|
|
||||||
import java.io.DataInput
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
class ZipEntry private constructor(
|
|
||||||
internal val version: UShort,
|
|
||||||
internal val versionNeeded: UShort,
|
|
||||||
internal val flags: UShort,
|
|
||||||
internal var compression: UShort,
|
|
||||||
internal val modificationTime: UShort,
|
|
||||||
internal val modificationDate: UShort,
|
|
||||||
internal var crc32: UInt,
|
|
||||||
internal var compressedSize: UInt,
|
|
||||||
internal var uncompressedSize: UInt,
|
|
||||||
internal val diskNumber: UShort,
|
|
||||||
internal val internalAttributes: UShort,
|
|
||||||
internal val externalAttributes: UInt,
|
|
||||||
internal var localHeaderOffset: UInt,
|
|
||||||
internal val fileName: String,
|
|
||||||
internal val extraField: ByteArray,
|
|
||||||
internal val fileComment: String,
|
|
||||||
internal var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
|
||||||
) {
|
|
||||||
internal val LFHSize: Int
|
|
||||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
|
||||||
|
|
||||||
internal val dataOffset: UInt
|
|
||||||
get() = localHeaderOffset + LFHSize.toUInt()
|
|
||||||
|
|
||||||
constructor(fileName: String) : this(
|
|
||||||
0x1403u, //made by unix, version 20
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0x0821u, //seems to be static time google uses, no idea
|
|
||||||
0x0221u, //same as above
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
0u,
|
|
||||||
fileName,
|
|
||||||
ByteArray(0),
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal const val CDE_HEADER_SIZE = 46
|
|
||||||
internal const val CDE_SIGNATURE = 0x02014b50u
|
|
||||||
|
|
||||||
internal const val LFH_HEADER_SIZE = 30
|
|
||||||
internal const val LFH_SIGNATURE = 0x04034b50u
|
|
||||||
|
|
||||||
internal fun fromCDE(input: DataInput): ZipEntry {
|
|
||||||
val signature = input.readUIntLE()
|
|
||||||
|
|
||||||
if (signature != CDE_SIGNATURE)
|
|
||||||
throw IllegalArgumentException("Input doesn't start with central directory entry signature")
|
|
||||||
|
|
||||||
val version = input.readUShortLE()
|
|
||||||
val versionNeeded = input.readUShortLE()
|
|
||||||
var flags = input.readUShortLE()
|
|
||||||
val compression = input.readUShortLE()
|
|
||||||
val modificationTime = input.readUShortLE()
|
|
||||||
val modificationDate = input.readUShortLE()
|
|
||||||
val crc32 = input.readUIntLE()
|
|
||||||
val compressedSize = input.readUIntLE()
|
|
||||||
val uncompressedSize = input.readUIntLE()
|
|
||||||
val fileNameLength = input.readUShortLE()
|
|
||||||
var fileName = ""
|
|
||||||
val extraFieldLength = input.readUShortLE()
|
|
||||||
val extraField = ByteArray(extraFieldLength.toInt())
|
|
||||||
val fileCommentLength = input.readUShortLE()
|
|
||||||
var fileComment = ""
|
|
||||||
val diskNumber = input.readUShortLE()
|
|
||||||
val internalAttributes = input.readUShortLE()
|
|
||||||
val externalAttributes = input.readUIntLE()
|
|
||||||
val localHeaderOffset = input.readUIntLE()
|
|
||||||
|
|
||||||
val variableFieldsLength =
|
|
||||||
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
|
||||||
|
|
||||||
if (variableFieldsLength > 0) {
|
|
||||||
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
|
||||||
input.readFully(fileNameBytes)
|
|
||||||
fileName = fileNameBytes.toString(Charsets.UTF_8)
|
|
||||||
|
|
||||||
input.readFully(extraField)
|
|
||||||
|
|
||||||
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
|
||||||
input.readFully(fileCommentBytes)
|
|
||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags = (flags and 0b1000u.inv()
|
|
||||||
.toUShort()) //disable data descriptor flag as they are not used
|
|
||||||
|
|
||||||
return ZipEntry(
|
|
||||||
version,
|
|
||||||
versionNeeded,
|
|
||||||
flags,
|
|
||||||
compression,
|
|
||||||
modificationTime,
|
|
||||||
modificationDate,
|
|
||||||
crc32,
|
|
||||||
compressedSize,
|
|
||||||
uncompressedSize,
|
|
||||||
diskNumber,
|
|
||||||
internalAttributes,
|
|
||||||
externalAttributes,
|
|
||||||
localHeaderOffset,
|
|
||||||
fileName,
|
|
||||||
extraField,
|
|
||||||
fileComment,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun readLocalExtra(buffer: ByteBuffer) {
|
|
||||||
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
localExtraField = ByteArray(buffer.getUShort().toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun toLFH(): ByteBuffer {
|
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(LFH_SIGNATURE)
|
|
||||||
buffer.putUShort(versionNeeded)
|
|
||||||
buffer.putUShort(flags)
|
|
||||||
buffer.putUShort(compression)
|
|
||||||
buffer.putUShort(modificationTime)
|
|
||||||
buffer.putUShort(modificationDate)
|
|
||||||
buffer.putUInt(crc32)
|
|
||||||
buffer.putUInt(compressedSize)
|
|
||||||
buffer.putUInt(uncompressedSize)
|
|
||||||
buffer.putUShort(nameBytes.size.toUShort())
|
|
||||||
buffer.putUShort(localExtraField.size.toUShort())
|
|
||||||
|
|
||||||
buffer.put(nameBytes)
|
|
||||||
buffer.put(localExtraField)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun toCDE(): ByteBuffer {
|
|
||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val buffer =
|
|
||||||
ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
|
||||||
|
|
||||||
buffer.putUInt(CDE_SIGNATURE)
|
|
||||||
buffer.putUShort(version)
|
|
||||||
buffer.putUShort(versionNeeded)
|
|
||||||
buffer.putUShort(flags)
|
|
||||||
buffer.putUShort(compression)
|
|
||||||
buffer.putUShort(modificationTime)
|
|
||||||
buffer.putUShort(modificationDate)
|
|
||||||
buffer.putUInt(crc32)
|
|
||||||
buffer.putUInt(compressedSize)
|
|
||||||
buffer.putUInt(uncompressedSize)
|
|
||||||
buffer.putUShort(nameBytes.size.toUShort())
|
|
||||||
buffer.putUShort(extraField.size.toUShort())
|
|
||||||
buffer.putUShort(commentBytes.size.toUShort())
|
|
||||||
buffer.putUShort(diskNumber)
|
|
||||||
buffer.putUShort(internalAttributes)
|
|
||||||
buffer.putUInt(externalAttributes)
|
|
||||||
buffer.putUInt(localHeaderOffset)
|
|
||||||
|
|
||||||
buffer.put(nameBytes)
|
|
||||||
buffer.put(extraField)
|
|
||||||
buffer.put(commentBytes)
|
|
||||||
|
|
||||||
buffer.flip()
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package app.revanced.lib
|
|
||||||
|
|
||||||
import app.revanced.lib.Options.setOptions
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
|
||||||
import app.revanced.patcher.patch.options.types.BooleanPatchOption.Companion.booleanPatchOption
|
|
||||||
import app.revanced.patcher.patch.options.types.StringPatchOption.Companion.stringPatchOption
|
|
||||||
import org.junit.jupiter.api.MethodOrderer
|
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder
|
|
||||||
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
|
||||||
internal object PatchOptionsTest {
|
|
||||||
private var patches = setOf(PatchOptionsTestPatch)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
fun serializeTest() {
|
|
||||||
assert(SERIALIZED_JSON == Options.serialize(patches))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
fun loadOptionsTest() {
|
|
||||||
patches.setOptions(CHANGED_JSON)
|
|
||||||
|
|
||||||
assert(PatchOptionsTestPatch.option1 == "test")
|
|
||||||
assert(PatchOptionsTestPatch.option2 == false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val SERIALIZED_JSON =
|
|
||||||
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\",\"value\":true}]}]"
|
|
||||||
|
|
||||||
private const val CHANGED_JSON =
|
|
||||||
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2\",\"value\":false}]}]"
|
|
||||||
|
|
||||||
object PatchOptionsTestPatch : BytecodePatch(name = "PatchOptionsTestPatch") {
|
|
||||||
var option1 by stringPatchOption("key1", null, "title1", "description1")
|
|
||||||
var option2 by booleanPatchOption("key2", true, "title2", "description2")
|
|
||||||
|
|
||||||
override fun execute(context: BytecodeContext) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package app.revanced.lib
|
|
||||||
|
|
||||||
import app.revanced.patcher.PatchSet
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
internal object PatchUtilsTest {
|
|
||||||
@Test
|
|
||||||
fun `return 'a' because it is the most common version`() {
|
|
||||||
val patches = arrayOf("a", "a", "c", "d", "a", "b", "c", "d", "a", "b", "c", "d")
|
|
||||||
.map { version -> newPatch("some.package", version) }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
assertEqualsVersion("a", patches, "some.package")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `return null because no patches were supplied`() {
|
|
||||||
assertEqualsVersion(null, emptySet<BytecodePatch>(), "some.package")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `return null because no patch is compatible with the supplied package name`() {
|
|
||||||
val patches = setOf(newPatch("some.package", "a"))
|
|
||||||
|
|
||||||
assertEqualsVersion(null, patches, "other.package")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `return null because no patch compatible package is constrained to a version`() {
|
|
||||||
val patches = setOf(
|
|
||||||
newPatch("other.package"),
|
|
||||||
newPatch("other.package"),
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEqualsVersion(null, patches, "other.package")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assertEqualsVersion(
|
|
||||||
expected: String?, patches: PatchSet, compatiblePackageName: String
|
|
||||||
) = assertEquals(expected, PatchUtils.getMostCommonCompatibleVersion(patches, compatiblePackageName))
|
|
||||||
|
|
||||||
private fun newPatch(packageName: String, vararg versions: String) = object : BytecodePatch(
|
|
||||||
compatiblePackages = setOf(CompatiblePackage(packageName, versions.toSet()))
|
|
||||||
) {
|
|
||||||
override fun execute(context: BytecodeContext) {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,4 +20,4 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
include("revanced-cli", "revanced-lib")
|
rootProject.name = "revanced-cli"
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
import app.revanced.cli.command.utility.UtilityCommand
|
import app.revanced.cli.command.utility.UtilityCommand
|
||||||
import app.revanced.lib.logging.Logger
|
import app.revanced.library.logging.Logger
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Command
|
import picocli.CommandLine.Command
|
||||||
import picocli.CommandLine.IVersionProvider
|
import picocli.CommandLine.IVersionProvider
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
import app.revanced.lib.Options
|
import app.revanced.library.Options
|
||||||
import app.revanced.lib.Options.setOptions
|
import app.revanced.library.Options.setOptions
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
@ -1,9 +1,9 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
import app.revanced.lib.ApkUtils
|
import app.revanced.library.ApkUtils
|
||||||
import app.revanced.lib.Options
|
import app.revanced.library.Options
|
||||||
import app.revanced.lib.Options.setOptions
|
import app.revanced.library.Options.setOptions
|
||||||
import app.revanced.lib.adb.AdbManager
|
import app.revanced.library.adb.AdbManager
|
||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.patcher.PatchSet
|
import app.revanced.patcher.PatchSet
|
||||||
import app.revanced.patcher.Patcher
|
import app.revanced.patcher.Patcher
|
@ -1,6 +1,6 @@
|
|||||||
package app.revanced.cli.command.utility
|
package app.revanced.cli.command.utility
|
||||||
|
|
||||||
import app.revanced.lib.adb.AdbManager
|
import app.revanced.library.adb.AdbManager
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
@ -1,6 +1,6 @@
|
|||||||
package app.revanced.cli.command.utility
|
package app.revanced.cli.command.utility
|
||||||
|
|
||||||
import app.revanced.lib.adb.AdbManager
|
import app.revanced.library.adb.AdbManager
|
||||||
import picocli.CommandLine.*
|
import picocli.CommandLine.*
|
||||||
import picocli.CommandLine.Help.Visibility.ALWAYS
|
import picocli.CommandLine.Help.Visibility.ALWAYS
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
Loadingβ¦
Reference in New Issue
Block a user