feat: Add networking module

This commit is contained in:
oSumAtrIX
2024-04-07 22:33:03 +02:00
parent fe3e1c9dc8
commit 42de60e95b
58 changed files with 1880 additions and 189 deletions

View File

@@ -0,0 +1,337 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/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/library/ApkSigner$Signer {
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
}
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
public fun <init> (Ljava/io/File;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;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 class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCommonName ()Ljava/lang/String;
public final fun getValidUntil ()Ljava/util/Date;
}
public final class app/revanced/library/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/library/Options {
public static final field INSTANCE Lapp/revanced/library/Options;
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
public static synthetic fun serialize$default (Lapp/revanced/library/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/library/Options$Patch {
public final fun getOptions ()Ljava/util/List;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/library/Options$Patch$Option {
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/library/PatchUtils {
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json {
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
public final fun getCompatiblePackages ()Ljava/util/Set;
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Ljava/util/Map;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public final fun setRequiresIntegrations (Z)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
}
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
}
public final class app/revanced/library/Utils {
public static final field INSTANCE Lapp/revanced/library/Utils;
public final fun isAndroidEnvironment ()Z
}
public abstract class app/revanced/library/adb/AdbManager {
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/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/library/adb/AdbManager$Companion {
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
}
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
}
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
}
public abstract interface class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService : android/os/IInterface {
public static final field DESCRIPTOR Ljava/lang/String;
public abstract fun getFileSystemService ()Landroid/os/IBinder;
}
public class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Default : app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public fun getFileSystemService ()Landroid/os/IBinder;
}
public abstract class app/revanced/library/installation/command/ILocalShellCommandRunnerRootService$Stub : android/os/Binder, app/revanced/library/installation/command/ILocalShellCommandRunnerRootService {
public fun <init> ()V
public fun asBinder ()Landroid/os/IBinder;
public static fun asInterface (Landroid/os/IBinder;)Lapp/revanced/library/installation/command/ILocalShellCommandRunnerRootService;
public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z
}
public final class app/revanced/library/installation/command/LocalShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner, android/content/ServiceConnection, java/io/Closeable {
public fun close ()V
public fun onServiceConnected (Landroid/content/ComponentName;Landroid/os/IBinder;)V
public fun onServiceDisconnected (Landroid/content/ComponentName;)V
}
public abstract interface class app/revanced/library/installation/command/RunResult {
public abstract fun getError ()Ljava/lang/String;
public abstract fun getExitCode ()I
public abstract fun getOutput ()Ljava/lang/String;
public abstract fun waitFor ()V
}
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
}
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
protected final fun getLogger ()Ljava/util/logging/Logger;
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
}
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
public final fun getException ()Ljava/lang/Exception;
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public class app/revanced/library/installation/installer/Installation {
public final fun getApkFilePath ()Ljava/lang/String;
}
public abstract class app/revanced/library/installation/installer/Installer {
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getLogger ()Ljava/util/logging/Logger;
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/Installer$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/library/installation/installer/LocalInstaller : app/revanced/library/installation/installer/Installer, java/io/Closeable {
public static final field Companion Lapp/revanced/library/installation/installer/LocalInstaller$Companion;
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public fun close ()V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/LocalInstaller$Companion {
}
public final class app/revanced/library/installation/installer/LocalInstallerResult {
public final fun getExtra ()Ljava/lang/String;
public final fun getPackageName ()Ljava/lang/String;
public final fun getPmStatus ()I
}
public final class app/revanced/library/installation/installer/LocalInstallerService : android/app/Service {
public fun <init> ()V
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onStartCommand (Landroid/content/Intent;II)I
}
public final class app/revanced/library/installation/installer/LocalMountInstaller : app/revanced/library/installation/installer/MountInstaller, java/io/Closeable {
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
}
public final class app/revanced/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/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/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

283
library/api/jvm/library.api Normal file
View File

@@ -0,0 +1,283 @@
public final class app/revanced/library/ApkSigner {
public static final field INSTANCE Lapp/revanced/library/ApkSigner;
public final fun newApkSigner (Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/lang/String;Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newApkSigner (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$Signer;
public final fun newKeyStore (Ljava/io/OutputStream;Ljava/lang/String;Ljava/util/Set;)V
public final fun newKeyStore (Ljava/util/Set;)Ljava/security/KeyStore;
public final fun newPrivateKeyCertificatePair (Ljava/lang/String;Ljava/util/Date;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readKeyStore (Ljava/io/InputStream;Ljava/lang/String;)Ljava/security/KeyStore;
public final fun readPrivateKeyCertificatePair (Ljava/security/KeyStore;Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/ApkSigner$KeyStoreEntry {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun getAlias ()Ljava/lang/String;
public final fun getPassword ()Ljava/lang/String;
public final fun getPrivateKeyCertificatePair ()Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
}
public final class app/revanced/library/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/library/ApkSigner$Signer {
public final fun signApk (Lcom/android/tools/build/apkzlib/zip/ZFile;)V
public final fun signApk (Ljava/io/File;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;)V
}
public final class app/revanced/library/ApkUtils {
public static final field INSTANCE Lapp/revanced/library/ApkUtils;
public final fun applyTo (Lapp/revanced/patcher/PatcherResult;Ljava/io/File;)V
public final fun newPrivateKeyCertificatePair (Lapp/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun readPrivateKeyCertificatePairFromKeyStore (Lapp/revanced/library/ApkUtils$KeyStoreDetails;)Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;
public final fun sign (Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Lapp/revanced/library/ApkUtils$SigningOptions;)V
public final fun sign (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkSigner$PrivateKeyCertificatePair;)V
public final fun signApk (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Lapp/revanced/library/ApkUtils$KeyStoreDetails;)V
}
public final class app/revanced/library/ApkUtils$KeyStoreDetails {
public fun <init> (Ljava/io/File;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;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 class app/revanced/library/ApkUtils$PrivateKeyCertificatePairDetails {
public fun <init> ()V
public fun <init> (Ljava/lang/String;Ljava/util/Date;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Date;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getCommonName ()Ljava/lang/String;
public final fun getValidUntil ()Ljava/util/Date;
}
public final class app/revanced/library/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/library/Options {
public static final field INSTANCE Lapp/revanced/library/Options;
public final fun deserialize (Ljava/lang/String;)[Lapp/revanced/library/Options$Patch;
public final fun serialize (Ljava/util/Set;Z)Ljava/lang/String;
public static synthetic fun serialize$default (Lapp/revanced/library/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/library/Options$Patch {
public final fun getOptions ()Ljava/util/List;
public final fun getPatchName ()Ljava/lang/String;
}
public final class app/revanced/library/Options$Patch$Option {
public final fun getKey ()Ljava/lang/String;
public final fun getValue ()Ljava/lang/Object;
}
public final class app/revanced/library/PatchUtils {
public static final field INSTANCE Lapp/revanced/library/PatchUtils;
public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map;
public static synthetic fun getMostCommonCompatibleVersions$default (Lapp/revanced/library/PatchUtils;Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json {
public static final field INSTANCE Lapp/revanced/library/PatchUtils$Json;
public final fun deserialize (Ljava/io/InputStream;Ljava/lang/Class;)Ljava/util/Set;
public final fun serialize (Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;)V
public static synthetic fun serialize$default (Lapp/revanced/library/PatchUtils$Json;Ljava/util/Set;Lkotlin/jvm/functions/Function1;ZLjava/io/OutputStream;ILjava/lang/Object;)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch : app/revanced/library/PatchUtils$Json$JsonPatch {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$Companion;
public final fun getCompatiblePackages ()Ljava/util/Set;
public final fun getDependencies ()Ljava/util/Set;
public final fun getDescription ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
public final fun getOptions ()Ljava/util/Map;
public final fun getRequiresIntegrations ()Z
public final fun getUse ()Z
public final fun setRequiresIntegrations (Z)V
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$Companion {
public final fun fromPatch (Lapp/revanced/patcher/patch/Patch;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption {
public static final field Companion Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion;
public final fun getDefault ()Ljava/lang/Object;
public final fun getDescription ()Ljava/lang/String;
public final fun getKey ()Ljava/lang/String;
public final fun getRequired ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getValueType ()Ljava/lang/String;
public final fun getValues ()Ljava/util/Map;
}
public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption$Companion {
public final fun fromPatchOption (Lapp/revanced/patcher/patch/options/PatchOption;)Lapp/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPatchOption;
}
public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch {
}
public final class app/revanced/library/Utils {
public static final field INSTANCE Lapp/revanced/library/Utils;
public final fun isAndroidEnvironment ()Z
}
public abstract class app/revanced/library/adb/AdbManager {
public static final field Companion Lapp/revanced/library/adb/AdbManager$Companion;
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
protected abstract fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/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/library/adb/AdbManager$Companion {
public final fun getAdbManager (Ljava/lang/String;Z)Lapp/revanced/library/adb/AdbManager;
public static synthetic fun getAdbManager$default (Lapp/revanced/library/adb/AdbManager$Companion;Ljava/lang/String;ZILjava/lang/Object;)Lapp/revanced/library/adb/AdbManager;
}
public final class app/revanced/library/adb/AdbManager$DeviceNotFoundException : java/lang/Exception {
public fun <init> ()V
}
public final class app/revanced/library/adb/AdbManager$FailedToFindInstalledPackageException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$PackageNameRequiredException : java/lang/Exception {
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager : app/revanced/library/adb/AdbManager {
public static final field Utils Lapp/revanced/library/adb/AdbManager$RootAdbManager$Utils;
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/adb/AdbManager$RootAdbManager$Utils {
}
public final class app/revanced/library/adb/AdbManager$UserAdbManager : app/revanced/library/adb/AdbManager {
public synthetic fun getInstaller ()Lapp/revanced/library/installation/installer/Installer;
public fun install (Lapp/revanced/library/adb/AdbManager$Apk;)Lkotlin/jvm/functions/Function1;
public fun uninstall (Ljava/lang/String;)Lkotlin/jvm/functions/Function1;
}
public final class app/revanced/library/installation/command/AdbShellCommandRunner : app/revanced/library/installation/command/ShellCommandRunner {
}
public abstract interface class app/revanced/library/installation/command/RunResult {
public abstract fun getError ()Ljava/lang/String;
public abstract fun getExitCode ()I
public abstract fun getOutput ()Ljava/lang/String;
public abstract fun waitFor ()V
}
public final class app/revanced/library/installation/command/RunResult$DefaultImpls {
public static fun waitFor (Lapp/revanced/library/installation/command/RunResult;)V
}
public abstract class app/revanced/library/installation/command/ShellCommandRunner {
protected final fun getLogger ()Ljava/util/logging/Logger;
protected abstract fun runCommand (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
}
public final class app/revanced/library/installation/installer/AdbInstaller : app/revanced/library/installation/installer/Installer {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/library/installation/installer/AdbInstallerResult {
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Failure : app/revanced/library/installation/installer/AdbInstallerResult {
public final fun getException ()Ljava/lang/Exception;
}
public final class app/revanced/library/installation/installer/AdbInstallerResult$Success : app/revanced/library/installation/installer/AdbInstallerResult {
public static final field INSTANCE Lapp/revanced/library/installation/installer/AdbInstallerResult$Success;
}
public final class app/revanced/library/installation/installer/AdbMountInstaller : app/revanced/library/installation/installer/MountInstaller {
public fun <init> ()V
public fun <init> (Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public class app/revanced/library/installation/installer/Installation {
public final fun getApkFilePath ()Ljava/lang/String;
}
public abstract class app/revanced/library/installation/installer/Installer {
public abstract fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getLogger ()Ljava/util/logging/Logger;
public abstract fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/library/installation/installer/Installer$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/library/installation/installer/MountInstallation : app/revanced/library/installation/installer/Installation {
public final fun getInstalledApkFilePath ()Ljava/lang/String;
public final fun getMounted ()Z
}
public abstract class app/revanced/library/installation/installer/MountInstaller : app/revanced/library/installation/installer/Installer {
public fun getInstallation (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun getShellCommandRunner ()Lapp/revanced/library/installation/command/ShellCommandRunner;
public fun install (Lapp/revanced/library/installation/installer/Installer$Apk;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun invoke (Ljava/lang/String;)Lapp/revanced/library/installation/command/RunResult;
protected final fun move (Ljava/io/File;Ljava/lang/String;)V
public fun uninstall (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected final fun write (Ljava/lang/String;Ljava/lang/String;)V
}
public final class app/revanced/library/installation/installer/MountInstallerResult : java/lang/Enum {
public static final field FAILURE Lapp/revanced/library/installation/installer/MountInstallerResult;
public static final field SUCCESS Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lapp/revanced/library/installation/installer/MountInstallerResult;
public static fun values ()[Lapp/revanced/library/installation/installer/MountInstallerResult;
}
public final class app/revanced/library/logging/Logger {
public static final field INSTANCE Lapp/revanced/library/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/library/logging/Logger;Ljava/lang/String;ILjava/lang/Object;)V
}

123
library/build.gradle.kts Normal file
View File

@@ -0,0 +1,123 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
alias(libs.plugins.binary.compatibility.validator)
`maven-publish`
signing
}
kotlin {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
}
androidTarget {
compilations.all {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}
publishLibraryVariants("release")
}
sourceSets {
androidMain.dependencies {
implementation(libs.libsu.nio)
implementation(libs.libsu.service)
implementation(libs.core.ktx)
}
commonMain.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.reflect)
implementation(libs.jadb) // Fork with Shell v2 support.
implementation(libs.bcpkix.jdk15on)
implementation(libs.jackson.module.kotlin)
implementation(libs.apkzlib)
implementation(libs.apksig)
implementation(libs.guava)
}
commonTest.dependencies {
implementation(libs.revanced.patcher)
implementation(libs.kotlin.test.junit)
}
}
}
android {
namespace = "app.revanced.library"
compileSdk = 34
defaultConfig {
minSdk = 26
}
buildFeatures {
aidl = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
java {
targetCompatibility = JavaVersion.VERSION_11
}
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-library")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications {
create<MavenPublication>("revanced-library-publication") {
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-library.git"
developerConnection = "scm:git:git@github.com:revanced/revanced-library.git"
url = "https://github.com/revanced/revanced-library"
}
}
}
}
}
signing {
useGpgCmd()
sign(publishing.publications["revanced-library-publication"])
}

View File

@@ -0,0 +1,5 @@
package app.revanced.library.installation.command;
interface ILocalShellCommandRunnerRootService {
IBinder getFileSystemService();
}

View File

@@ -0,0 +1,88 @@
package app.revanced.library.installation.command
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.BuilderImpl
import com.topjohnwu.superuser.ipc.RootService
import com.topjohnwu.superuser.nio.FileSystemManager
import java.io.Closeable
import java.io.File
import java.io.InputStream
/**
* The [LocalShellCommandRunner] for running commands locally on the device.
*
* @param context The [Context] to use for binding to the [RootService].
* @param onReady A callback to be invoked when [LocalShellCommandRunner] is ready to be used.
* @throws IllegalStateException If the main shell was already created
*
* @see ShellCommandRunner
*/
class LocalShellCommandRunner internal constructor(
private val context: Context,
private val onReady: () -> Unit,
) : ShellCommandRunner(), ServiceConnection, Closeable {
private var fileSystemManager: FileSystemManager? = null
init {
logger.info("Binding to RootService")
val intent = Intent(context, LocalShellCommandRunnerRootService::class.java)
RootService.bind(intent, this)
}
override fun runCommand(command: String) = shell.newJob().add(command).exec().let {
object : RunResult {
override val exitCode = it.code
override val output by lazy { it.out.joinToString("\n") }
override val error by lazy { it.err.joinToString("\n") }
}
}
override fun hasRootPermission() = shell.isRoot
/**
* Writes the given [content] to the given [targetFilePath].
*
* @param content The [InputStream] to write.
* @param targetFilePath The path to write to.
* @throws NotReadyException If the [LocalShellCommandRunner] is not ready yet.
*/
override fun write(content: InputStream, targetFilePath: String) {
fileSystemManager?.let {
it.getFile(targetFilePath).newOutputStream().use { outputStream ->
content.copyTo(outputStream)
}
} ?: throw NotReadyException("FileSystemManager service is not ready yet")
}
override fun move(file: File, targetFilePath: String) {
invoke("mv ${file.absolutePath} $targetFilePath")
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val ipc = ILocalShellCommandRunnerRootService.Stub.asInterface(service)
fileSystemManager = FileSystemManager.getRemote(ipc.fileSystemService)
logger.info("LocalShellCommandRunner service is ready")
onReady()
}
override fun onServiceDisconnected(name: ComponentName?) {
fileSystemManager = null
logger.info("LocalShellCommandRunner service is disconnected")
}
override fun close() = RootService.unbind(this)
private companion object {
private val shell = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER).build()
}
internal class NotReadyException internal constructor(message: String) : Exception(message)
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.installation.command
import android.content.Intent
import com.topjohnwu.superuser.ipc.RootService
import com.topjohnwu.superuser.nio.FileSystemManager
/**
* The [RootService] for the [LocalShellCommandRunner].
*/
internal class LocalShellCommandRunnerRootService : RootService() {
override fun onBind(intent: Intent) = object : ILocalShellCommandRunnerRootService.Stub() {
override fun getFileSystemService() =
FileSystemManager.getService()
}
}

View File

@@ -0,0 +1,104 @@
package app.revanced.library.installation.installer
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import app.revanced.library.installation.installer.Installer.Apk
import java.io.Closeable
import java.io.File
/**
* [LocalInstaller] for installing and uninstalling [Apk] files locally.
*
* @param context The [Context] to use for installing and uninstalling.
* @param onResult The callback to be invoked when the [Apk] is installed or uninstalled.
*
* @see Installer
*/
@Suppress("unused")
class LocalInstaller(
private val context: Context,
onResult: (result: LocalInstallerResult) -> Unit,
) : Installer<Unit, Installation>(), Closeable {
private val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val pmStatus = intent.getIntExtra(LocalInstallerService.EXTRA_STATUS, -999)
val extra = intent.getStringExtra(LocalInstallerService.EXTRA_STATUS_MESSAGE)!!
val packageName = intent.getStringExtra(LocalInstallerService.EXTRA_PACKAGE_NAME)!!
onResult.invoke(LocalInstallerResult(pmStatus, extra, packageName))
}
}
private val intentSender
get() = PendingIntent.getService(
context,
0,
Intent(context, LocalInstallerService::class.java),
PendingIntent.FLAG_UPDATE_CURRENT,
).intentSender
init {
ContextCompat.registerReceiver(
context,
broadcastReceiver,
IntentFilter().apply {
addAction(LocalInstallerService.ACTION)
},
ContextCompat.RECEIVER_NOT_EXPORTED,
)
}
override suspend fun install(apk: Apk) {
logger.info("Installing ${apk.file.name}")
val packageInstaller = context.packageManager.packageInstaller
packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session ->
session.writeApk(apk.file)
session.commit(intentSender)
}
}
@SuppressLint("MissingPermission")
override suspend fun uninstall(packageName: String) {
logger.info("Uninstalling $packageName")
val packageInstaller = context.packageManager.packageInstaller
packageInstaller.uninstall(packageName, intentSender)
}
override suspend fun getInstallation(packageName: String) = try {
val packageInfo = context.packageManager.getPackageInfo(packageName, 0)
Installation(packageInfo.applicationInfo.sourceDir)
} catch (e: PackageManager.NameNotFoundException) {
null
}
override fun close() = context.unregisterReceiver(broadcastReceiver)
companion object {
private val sessionParams = PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
).apply {
setInstallReason(PackageManager.INSTALL_REASON_USER)
}
private fun PackageInstaller.Session.writeApk(apk: File) {
apk.inputStream().use { inputStream ->
openWrite(apk.name, 0, apk.length()).use { outputStream ->
inputStream.copyTo(outputStream, 1024 * 1024)
fsync(outputStream)
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] locally using [LocalInstaller].
*
* @param pmStatus The status code returned by the package manager.
* @param extra The extra information returned by the package manager.
* @param packageName The package name of the installed app.
*
* @see LocalInstaller
*/
@Suppress("MemberVisibilityCanBePrivate")
class LocalInstallerResult internal constructor(val pmStatus: Int, val extra: String, val packageName: String)

View File

@@ -0,0 +1,57 @@
package app.revanced.library.installation.installer
import android.app.Service
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.os.IBinder
class LocalInstallerService : Service() {
override fun onStartCommand(
intent: Intent, flags: Int, startId: Int
): Int {
val extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)
val extraStatusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val extraPackageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)
when (extraStatus) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
startActivity(
if (Build.VERSION.SDK_INT >= 33) {
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(Intent.EXTRA_INTENT)
}?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
else -> {
sendBroadcast(
Intent().apply {
action = ACTION
`package` = packageName
putExtra(EXTRA_STATUS, extraStatus)
putExtra(EXTRA_STATUS_MESSAGE, extraStatusMessage)
putExtra(EXTRA_PACKAGE_NAME, extraPackageName)
}
)
}
}
stopSelf()
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
internal companion object {
internal const val ACTION = "PACKAGE_INSTALLER_ACTION"
internal const val EXTRA_STATUS = "EXTRA_STATUS"
internal const val EXTRA_STATUS_MESSAGE = "EXTRA_STATUS_MESSAGE"
internal const val EXTRA_PACKAGE_NAME = "EXTRA_PACKAGE_NAME"
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.library.installation.installer
import android.content.Context
import app.revanced.library.installation.command.LocalShellCommandRunner
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
import com.topjohnwu.superuser.ipc.RootService
import java.io.Closeable
/**
* [LocalMountInstaller] for installing and uninstalling [Apk] files locally with using root permissions by mounting.
*
* @param context The [Context] to use for binding to the [RootService].
* @param onReady A callback to be invoked when [LocalMountInstaller] is ready to be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see Installer
* @see LocalShellCommandRunner
*/
@Suppress("unused")
class LocalMountInstaller(
context: Context,
onReady: LocalMountInstaller.() -> Unit = {},
) : MountInstaller(
{ installer ->
LocalShellCommandRunner(context) {
(installer as LocalMountInstaller).onReady()
}
},
),
Closeable {
override fun close() = (shellCommandRunner as LocalShellCommandRunner).close()
}

View File

@@ -0,0 +1,369 @@
package app.revanced.library
import com.android.apksig.ApkSigner.SignerConfig
import com.android.tools.build.apkzlib.sign.SigningExtension
import com.android.tools.build.apkzlib.sign.SigningOptions
import com.android.tools.build.apkzlib.zip.ZFile
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
/**
* Utility class for reading or writing keystore files and entries as well as signing APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkSigner {
private val logger = Logger.getLogger(ApkSigner::class.java.name)
init {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
}
}
private fun newKeyStoreInstance() = KeyStore.getInstance("BKS", BouncyCastleProvider.PROVIDER_NAME)
/**
* Create a new keystore with a new keypair.
*
* @param entries The entries to add to the keystore.
*
* @return The created keystore.
*
* @see KeyStoreEntry
* @see KeyStore
*/
fun newKeyStore(entries: Set<KeyStoreEntry>): KeyStore {
logger.fine("Creating keystore")
return newKeyStoreInstance().apply {
load(null)
entries.forEach { entry ->
// Add all entries to the keystore.
setKeyEntry(
entry.alias,
entry.privateKeyCertificatePair.privateKey,
entry.password.toCharArray(),
arrayOf(entry.privateKeyCertificatePair.certificate),
)
}
}
}
/**
* 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.
*
* @see KeyStore
*/
fun readKeyStore(
keyStoreInputStream: InputStream,
keyStorePassword: String?,
): KeyStore {
logger.fine("Reading keystore")
return newKeyStoreInstance().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 private key and certificate pair.
*
* @param commonName The common name of the certificate.
* @param validUntil The date until which the certificate is valid.
*
* @return The newly created private key and certificate pair.
*
* @see PrivateKeyCertificatePair
*/
fun newPrivateKeyCertificatePair(
commonName: String,
validUntil: Date,
): PrivateKeyCertificatePair {
logger.fine("Creating certificate for $commonName")
// Generate a new key pair.
val keyPair = KeyPairGenerator.getInstance("RSA").apply {
initialize(4096)
}.generateKeyPair()
val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(keyPair.private)
val name = X500Name("CN=$commonName")
val certificateHolder = X509v3CertificateBuilder(
name,
BigInteger.valueOf(SecureRandom().nextLong()),
Date(System.currentTimeMillis()),
validUntil,
Locale.ENGLISH,
name,
SubjectPublicKeyInfo.getInstance(keyPair.public.encoded),
).build(contentSigner)
val certificate = JcaX509CertificateConverter().getCertificate(certificateHolder)
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.
*
* @see PrivateKeyCertificatePair
* @see KeyStore
*/
fun readPrivateKeyCertificatePair(
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 entry with 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 [Signer].
*
* @param signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*
* @return The new [Signer].
*
* @see PrivateKeyCertificatePair
* @see Signer
*/
fun newApkSigner(
signer: String,
privateKeyCertificatePair: PrivateKeyCertificatePair,
) = Signer(
com.android.apksig.ApkSigner.Builder(
listOf(
SignerConfig.Builder(
signer,
privateKeyCertificatePair.privateKey,
listOf(privateKeyCertificatePair.certificate),
).build(),
),
),
)
/**
* 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.
*/
@Deprecated("This method will be removed in the future.")
fun readKeyCertificatePair(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = readPrivateKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword)
/**
* 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.
*/
@Deprecated("This method will be removed in the future.")
fun newKeyStore(
keyStoreOutputStream: OutputStream,
keyStorePassword: String?,
entries: Set<KeyStoreEntry>,
) = newKeyStore(entries).store(
keyStoreOutputStream,
keyStorePassword?.toCharArray(),
)
/**
* Create a new [Signer].
*
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*
* @return The new [Signer].
*
* @see PrivateKeyCertificatePair
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(privateKeyCertificatePair: PrivateKeyCertificatePair) =
Signer(
SigningExtension(
SigningOptions.builder()
.setMinSdkVersion(21) // TODO: Extracting from the target APK would be ideal.
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setCertificates(privateKeyCertificatePair.certificate)
.setKey(privateKeyCertificatePair.privateKey)
.build(),
),
)
/**
* Create a new [Signer].
*
* @param signer The name of the signer.
* @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.
*
* @return The new [Signer].
*
* @see KeyStore
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(
signer: String,
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = newApkSigner(signer, readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
/**
* Create a new [Signer].
*
* @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.
*
* @return The new [Signer].
*
* @see KeyStore
* @see Signer
*/
@Deprecated("This method will be removed in the future.")
fun newApkSigner(
keyStore: KeyStore,
keyStoreEntryAlias: String,
keyStoreEntryPassword: String,
) = newApkSigner("ReVanced", readKeyCertificatePair(keyStore, keyStoreEntryAlias, keyStoreEntryPassword))
/**
* 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,
)
/**
* A private key and certificate pair.
*
* @param privateKey The private key.
* @param certificate The certificate.
*/
class PrivateKeyCertificatePair(
val privateKey: PrivateKey,
val certificate: X509Certificate,
)
class Signer {
private val signerBuilder: com.android.apksig.ApkSigner.Builder?
private val signingExtension: SigningExtension?
internal constructor(signerBuilder: com.android.apksig.ApkSigner.Builder) {
this.signerBuilder = signerBuilder
signingExtension = null
}
fun signApk(inputApkFile: File, outputApkFile: File) {
logger.info("Signing APK")
signerBuilder?.setInputApk(inputApkFile)?.setOutputApk(outputApkFile)?.build()?.sign()
}
@Deprecated("This constructor will be removed in the future.")
internal constructor(signingExtension: SigningExtension) {
signerBuilder = null
this.signingExtension = signingExtension
}
/**
* Sign an APK file.
*
* @param apkFile The APK file to sign.
*/
@Deprecated("This method will be removed in the future.")
fun signApk(apkFile: File) = ZFile.openReadWrite(apkFile).use {
@Suppress("DEPRECATION")
signApk(it)
}
/**
* Sign an APK file.
*
* @param apkZFile The APK [ZFile] to sign.
*/
@Deprecated("This method will be removed in the future.")
fun signApk(apkZFile: ZFile) {
logger.info("Signing ${apkZFile.file.name}")
signingExtension?.register(apkZFile)
}
}
}

View File

@@ -0,0 +1,281 @@
package app.revanced.library
import app.revanced.library.ApkSigner.newPrivateKeyCertificatePair
import app.revanced.patcher.PatcherResult
import com.android.tools.build.apkzlib.zip.AlignmentRules
import com.android.tools.build.apkzlib.zip.StoredEntry
import com.android.tools.build.apkzlib.zip.ZFile
import com.android.tools.build.apkzlib.zip.ZFileOptions
import java.io.File
import java.util.*
import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
/**
* Utility functions to work with APK files.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object ApkUtils {
private val logger = Logger.getLogger(ApkUtils::class.java.name)
private const val LIBRARY_EXTENSION = ".so"
// Alignment for native libraries.
private const val LIBRARY_ALIGNMENT = 1024 * 4
// Alignment for all other files.
private const val DEFAULT_ALIGNMENT = 4
// Prefix for resources.
private const val RES_PREFIX = "res/"
private val zFileOptions =
ZFileOptions().setAlignmentRule(
AlignmentRules.compose(
AlignmentRules.constantForSuffix(LIBRARY_EXTENSION, LIBRARY_ALIGNMENT),
AlignmentRules.constant(DEFAULT_ALIGNMENT),
),
)
/**
* Applies the [PatcherResult] to the given [apkFile].
*
* The order of operation is as follows:
* 1. Write patched dex files.
* 2. Delete all resources in the target APK
* 3. Merge resources.apk compiled by AAPT.
* 4. Write raw resources.
* 5. Delete resources staged for deletion.
* 6. Realign the APK.
*
* @param apkFile The file to apply the patched files to.
*/
fun PatcherResult.applyTo(apkFile: File) {
ZFile.openReadWrite(apkFile, zFileOptions).use { targetApkZFile ->
dexFiles.forEach { dexFile ->
targetApkZFile.add(dexFile.name, dexFile.stream)
}
resources?.let { resources ->
// Add resources compiled by AAPT.
resources.resourcesApk?.let { resourcesApk ->
ZFile.openReadOnly(resourcesApk).use { resourcesApkZFile ->
// Delete all resources in the target APK before merging the new ones.
// This is necessary because the resources.apk renames resources.
// So unless, the old resources are deleted, there will be orphaned resources in the APK.
// It is not necessary, but for the sake of cleanliness, it is done.
targetApkZFile.entries().filter { entry ->
entry.centralDirectoryHeader.name.startsWith(RES_PREFIX)
}.forEach(StoredEntry::delete)
targetApkZFile.mergeFrom(resourcesApkZFile) { false }
}
}
// Add resources not compiled by AAPT.
resources.otherResources?.let { otherResources ->
targetApkZFile.addAllRecursively(otherResources) { file ->
file.relativeTo(otherResources).invariantSeparatorsPath !in resources.doNotCompress
}
}
// Delete resources that were staged for deletion.
if (resources.deleteResources.isNotEmpty()) {
targetApkZFile.entries().filter { entry ->
resources.deleteResources.any { shouldDelete -> shouldDelete(entry.centralDirectoryHeader.name) }
}.forEach(StoredEntry::delete)
}
}
logger.info("Aligning APK")
targetApkZFile.realign()
logger.fine("Writing changes")
}
}
/**
* Creates a new private key and certificate pair and saves it to the keystore in [keyStoreDetails].
*
* @param privateKeyCertificatePairDetails The details for the private key and certificate pair.
* @param keyStoreDetails The details for the keystore.
*
* @return The newly created private key and certificate pair.
*/
@Deprecated("This method will be removed in the future.")
fun newPrivateKeyCertificatePair(
privateKeyCertificatePairDetails: PrivateKeyCertificatePairDetails,
keyStoreDetails: KeyStoreDetails,
) = newPrivateKeyCertificatePair(
privateKeyCertificatePairDetails.commonName,
privateKeyCertificatePairDetails.validUntil,
).also { privateKeyCertificatePair ->
ApkSigner.newKeyStore(
setOf(
ApkSigner.KeyStoreEntry(
keyStoreDetails.alias,
keyStoreDetails.password,
privateKeyCertificatePair,
),
),
).store(
keyStoreDetails.keyStore.outputStream(),
keyStoreDetails.keyStorePassword?.toCharArray(),
)
}
/**
* Reads the private key and certificate pair from an existing keystore.
*
* @param keyStoreDetails The details for the keystore.
*
* @return The private key and certificate pair.
*/
@Deprecated("This method will be removed in the future.")
fun readPrivateKeyCertificatePairFromKeyStore(
keyStoreDetails: KeyStoreDetails,
) = ApkSigner.readPrivateKeyCertificatePair(
ApkSigner.readKeyStore(
keyStoreDetails.keyStore.inputStream(),
keyStoreDetails.keyStorePassword,
),
keyStoreDetails.alias,
keyStoreDetails.password,
)
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
* If [KeyStoreDetails.keyStore] does not exist,
* a new private key and certificate pair will be created and saved to the keystore.
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signer The name of the signer.
* @param keyStoreDetails The details for the keystore.
*/
fun signApk(
inputApkFile: File,
outputApkFile: File,
signer: String,
keyStoreDetails: KeyStoreDetails,
) = ApkSigner.newApkSigner(
signer,
if (keyStoreDetails.keyStore.exists()) {
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
} else {
newPrivateKeyCertificatePair(PrivateKeyCertificatePairDetails(), keyStoreDetails)
},
).signApk(inputApkFile, outputApkFile)
@Deprecated("This method will be removed in the future.")
private fun readOrNewPrivateKeyCertificatePair(
signingOptions: SigningOptions,
): ApkSigner.PrivateKeyCertificatePair {
val privateKeyCertificatePairDetails = PrivateKeyCertificatePairDetails(
signingOptions.alias,
PrivateKeyCertificatePairDetails().validUntil,
)
val keyStoreDetails = KeyStoreDetails(
signingOptions.keyStore,
signingOptions.keyStorePassword,
signingOptions.alias,
signingOptions.password,
)
return if (keyStoreDetails.keyStore.exists()) {
readPrivateKeyCertificatePairFromKeyStore(keyStoreDetails)
} else {
newPrivateKeyCertificatePair(privateKeyCertificatePairDetails, keyStoreDetails)
}
}
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signer The name of the signer.
* @param privateKeyCertificatePair The private key and certificate pair to use for signing.
*/
@Deprecated("This method will be removed in the future.")
fun sign(
inputApkFile: File,
outputApkFile: File,
signer: String,
privateKeyCertificatePair: ApkSigner.PrivateKeyCertificatePair,
) = ApkSigner.newApkSigner(
signer,
privateKeyCertificatePair,
).signApk(inputApkFile, outputApkFile)
/**
* Signs the apk file with the given options.
*
* @param signingOptions The options to use for signing.
*/
@Deprecated("This method will be removed in the future.")
fun File.sign(signingOptions: SigningOptions) = ApkSigner.newApkSigner(
signingOptions.signer,
readOrNewPrivateKeyCertificatePair(signingOptions),
).signApk(this)
/**
* Signs [inputApkFile] with the given options and saves the signed apk to [outputApkFile].
*
* @param inputApkFile The apk file to sign.
* @param outputApkFile The file to save the signed apk to.
* @param signingOptions The options to use for signing.
*/
@Deprecated("This method will be removed in the future.")
fun sign(inputApkFile: File, outputApkFile: File, signingOptions: SigningOptions) = sign(
inputApkFile,
outputApkFile,
signingOptions.signer,
readOrNewPrivateKeyCertificatePair(signingOptions),
)
/**
* 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.
*/
@Deprecated("This class will be removed in the future.")
class SigningOptions(
val keyStore: File,
val keyStorePassword: String?,
val alias: String = "ReVanced Key",
val password: String = "",
val signer: String = "ReVanced",
)
/**
* Details for a keystore.
*
* @param keyStore The file to save the keystore to.
* @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.
*/
class KeyStoreDetails(
val keyStore: File,
val keyStorePassword: String? = null,
val alias: String = "ReVanced Key",
val password: String = "",
)
/**
* Details for a private key and certificate pair.
*
* @param commonName The common name for the certificate saved in the keystore.
* @param validUntil The date until which the certificate is valid.
*/
class PrivateKeyCertificatePairDetails(
val commonName: String = "ReVanced",
val validUntil: Date = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24),
)
}

View File

@@ -0,0 +1,43 @@
@file:Suppress("DeprecatedCallableAddReplaceWith")
package app.revanced.library
import app.revanced.library.installation.command.AdbShellCommandRunner
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.ShellProcessBuilder
import java.io.File
@Deprecated("Do not use this anymore. Instead use AdbCommandRunner.")
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())
}
@Suppress("DEPRECATION")
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.run(
command: String,
su: Boolean = true,
) = buildCommand(command, su).start()
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.hasSu() = AdbShellCommandRunner(this).hasRootPermission()
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.push(
file: File,
targetFilePath: String,
) = AdbShellCommandRunner(this).move(file, targetFilePath)
@Deprecated("Use AdbShellCommandRunner instead.")
internal fun JadbDevice.createFile(
targetFile: String,
content: String,
) = AdbShellCommandRunner(this).write(content.byteInputStream(), targetFile)

View File

@@ -0,0 +1,120 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package app.revanced.library
import app.revanced.library.Options.Patch.Option
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
@Suppress("unused")
object Options {
private val logger = Logger.getLogger(Options::class.java.name)
private val mapper = jacksonObjectMapper()
/**
* Serializes the options for a set of patches.
*
* @param patches The set 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 ->
val optionValue =
try {
option.value
} catch (e: PatchOptionException) {
logger.warning("Using default option value for the ${patch.name} patch: ${e.message}")
option.default
}
Option(option.key, optionValue)
},
)
}
// 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 to a set of patches.
*
* @param json The JSON string containing the options.
* @return A set of [Patch]s.
* @see Patch
*/
fun deserialize(json: String): Array<Patch> = mapper.readValue(json, Array<Patch>::class.java)
/**
* Sets the options for a set of patches.
*
* @param json The JSON string containing the options.
*/
fun PatchSet.setOptions(json: String) {
filter { it.options.any() }.let { patches ->
if (patches.isEmpty()) return
val jsonPatches =
deserialize(json).associate {
it.patchName to it.options.associate { option -> option.key to option.value }
}
patches.forEach { patch ->
jsonPatches[patch.name]?.let { jsonPatchOptions ->
jsonPatchOptions.forEach { (option, value) ->
try {
patch.options[option] = value
} catch (e: PatchOptionException) {
logger.warning("Could not set option value for the ${patch.name} patch: ${e.message}")
}
}
}
}
}
}
/**
* Sets the options for a set of patches.
*
* @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?)
}
}

View File

@@ -0,0 +1,169 @@
package app.revanced.library
import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.jvm.jvmName
typealias PackageName = String
typealias Version = String
typealias Count = Int
typealias VersionMap = LinkedHashMap<Version, Count>
typealias PackageNameMap = Map<PackageName, VersionMap>
/**
* Utility functions for working with patches.
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
object PatchUtils {
/**
* Get the count of versions for each compatible package from a supplied set of [patches] ordered by the most common version.
*
* @param patches The set of patches to check.
* @param packageNames The names of the compatible packages to include. If null, all packages will be included.
* @param countUnusedPatches Whether to count patches that are not used.
* @return A map of package names to a map of versions to their count.
*/
fun getMostCommonCompatibleVersions(
patches: PatchSet,
packageNames: Set<String>? = null,
countUnusedPatches: Boolean = false,
): PackageNameMap =
buildMap {
fun filterWantedPackages(compatiblePackages: Iterable<Patch.CompatiblePackage>): Iterable<Patch.CompatiblePackage> {
val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages
return compatiblePackages.filter { it.name in wantedPackages }
}
patches
.filter { it.use || countUnusedPatches }
.flatMap { it.compatiblePackages ?: emptyList() }
.let(::filterWantedPackages)
.forEach { compatiblePackage ->
if (compatiblePackage.versions?.isEmpty() == true) {
return@forEach
}
val versionMap = getOrPut(compatiblePackage.name) { linkedMapOf() }
compatiblePackage.versions?.let { versions ->
versions.forEach { version ->
versionMap[version] = versionMap.getOrDefault(version, 0) + 1
}
}
}
// Sort the version maps by the most common version.
forEach { (packageName, versionMap) ->
this[packageName] =
versionMap
.asIterable()
.sortedWith(compareByDescending { it.value })
.associate { it.key to it.value } as VersionMap
}
}
object Json {
private val mapper = jacksonObjectMapper()
/**
* Serializes a set of [Patch]es to a JSON string and writes it to an output stream.
*
* @param patches The set of [Patch]es to serialize.
* @param transform A function to transform the [Patch]es to [JsonPatch]es.
* @param prettyPrint Whether to pretty print the JSON.
* @param outputStream The output stream to write the JSON to.
*/
fun serialize(
patches: PatchSet,
transform: (Patch<*>) -> JsonPatch = { patch -> FullJsonPatch.fromPatch(patch) },
prettyPrint: Boolean = false,
outputStream: OutputStream,
) {
patches.map(transform).let { transformed ->
if (prettyPrint) {
mapper.writerWithDefaultPrettyPrinter().writeValue(outputStream, transformed)
} else {
mapper.writeValue(outputStream, transformed)
}
}
}
/**
* Deserializes a JSON string to a set of [FullJsonPatch]es from an input stream.
*
* @param inputStream The input stream to read the JSON from.
* @param jsonPatchElementClass The class of the [JsonPatch]es to deserialize.
* @return A set of [JsonPatch]es.
* @see FullJsonPatch
*/
fun <T : JsonPatch> deserialize(
inputStream: InputStream,
jsonPatchElementClass: Class<T>,
): Set<T> =
mapper.readValue(
inputStream,
mapper.typeFactory.constructCollectionType(Set::class.java, jsonPatchElementClass),
)
interface JsonPatch
/**
* A JSON representation of a [Patch].
* @see Patch
*/
class FullJsonPatch internal constructor(
val name: String?,
val description: String?,
val compatiblePackages: Set<Patch.CompatiblePackage>?,
val dependencies: Set<String>?,
val use: Boolean,
var requiresIntegrations: Boolean,
val options: Map<String, FullJsonPatchOption<*>>,
) : JsonPatch {
companion object {
fun fromPatch(patch: Patch<*>) =
FullJsonPatch(
patch.name,
patch.description,
patch.compatiblePackages,
buildSet { patch.dependencies?.forEach { add(it.jvmName) } },
patch.use,
patch.requiresIntegrations,
patch.options.mapValues { FullJsonPatchOption.fromPatchOption(it.value) },
)
}
/**
* A JSON representation of a [PatchOption].
* @see PatchOption
*/
class FullJsonPatchOption<T> internal constructor(
val key: String,
val default: T?,
val values: Map<String, T?>?,
val title: String?,
val description: String?,
val required: Boolean,
val valueType: String,
) {
companion object {
fun fromPatchOption(option: PatchOption<*>) =
FullJsonPatchOption(
option.key,
option.default,
option.values,
option.title,
option.description,
option.required,
option.valueType,
)
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
package app.revanced.library
/**
* Utils for the library.
*/
@Suppress("unused")
object Utils {
/**
* True if the environment is Android.
*/
val isAndroidEnvironment =
try {
Class.forName("android.app.Application")
true
} catch (e: ClassNotFoundException) {
false
}
}

View File

@@ -0,0 +1,144 @@
@file:Suppress("DEPRECATION")
package app.revanced.library.adb
import app.revanced.library.adb.AdbManager.Apk
import app.revanced.library.installation.installer.AdbInstaller
import app.revanced.library.installation.installer.AdbMountInstaller
import app.revanced.library.installation.installer.Constants.PLACEHOLDER
import app.revanced.library.installation.installer.Installer
import app.revanced.library.run
import se.vidstige.jadb.JadbDevice
import java.io.File
/**
* [AdbManager] to install and uninstall [Apk] files.
*
* @param deviceSerial The serial of the device. If null, the first connected device will be used.
*/
@Deprecated("Use an implementation of Installer instead.")
@Suppress("unused")
sealed class AdbManager private constructor(
@Suppress("UNUSED_PARAMETER") deviceSerial: String?,
) {
protected abstract val installer: Installer<*, *>
/**
* Installs the [Apk] file.
*
* @param apk The [Apk] file.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use Installer.install instead.")
open fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
/**
* Uninstalls the package.
*
* @param packageName The package name.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use Installer.uninstall instead.")
open fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
@Deprecated("Use Installer instead.")
companion object {
/**
* Gets an [AdbManager] for the supplied device serial.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
* @param root Whether to use root or not.
* @return The [AdbManager].
* @throws DeviceNotFoundException If the device can not be found.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("This is deprecated.")
fun getAdbManager(
deviceSerial: String? = null,
root: Boolean = false,
): AdbManager = if (root) RootAdbManager(deviceSerial) else UserAdbManager(deviceSerial)
}
/**
* Adb manager for rooted devices.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
@Deprecated("Use AdbMountInstaller instead.", ReplaceWith("AdbMountInstaller(deviceSerial)"))
class RootAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
override val installer = AdbMountInstaller(deviceSerial)
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbMountInstaller.install instead.")
override fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbMountInstaller.uninstall instead.")
override fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
@Deprecated("This is deprecated.")
companion object Utils {
private fun JadbDevice.run(
command: String,
with: String,
) = run(command.applyReplacement(with))
private fun String.applyReplacement(with: String) = replace(PLACEHOLDER, with)
}
}
/**
* Adb manager for non-rooted devices.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*/
@Deprecated("Use AdbInstaller instead.")
class UserAdbManager internal constructor(deviceSerial: String?) : AdbManager(deviceSerial) {
override val installer = AdbInstaller(deviceSerial)
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbInstaller.install instead.")
override fun install(apk: Apk) = suspend {
installer.install(Installer.Apk(apk.file, apk.packageName))
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use AdbInstaller.uninstall instead.")
override fun uninstall(packageName: String) = suspend {
installer.uninstall(packageName)
}
}
/**
* Apk file for [AdbManager].
*
* @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/
@Deprecated("Use Installer.Apk instead.")
class Apk(val file: File, val packageName: String? = null)
@Deprecated("Use AdbCommandRunner.DeviceNotFoundException instead.")
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) :
Exception(
deviceSerial?.let {
"The device with the ADB device serial \"$deviceSerial\" can not be found"
} ?: "No ADB device found",
)
@Deprecated("Use MountInstaller.FailedToFindInstalledPackageException instead.")
class FailedToFindInstalledPackageException internal constructor(packageName: String) :
Exception("Failed to find installed package \"$packageName\" because no activity was found")
@Deprecated("Use MountInstaller.PackageNameRequiredException instead.")
class PackageNameRequiredException internal constructor() :
Exception("Package name is required")
}

View File

@@ -0,0 +1,59 @@
package app.revanced.library.installation.command
import app.revanced.library.installation.installer.Utils
import se.vidstige.jadb.JadbDevice
import se.vidstige.jadb.RemoteFile
import java.io.File
import java.io.InputStream
/**
* [AdbShellCommandRunner] for running commands on a device remotely using ADB.
*
* @see ShellCommandRunner
*/
class AdbShellCommandRunner : ShellCommandRunner {
private val device: JadbDevice
/**
* Creates a [AdbShellCommandRunner] for the given device.
*
* @param device The device.
*/
internal constructor(device: JadbDevice) {
this.device = device
}
/**
* Creates a [AdbShellCommandRunner] for the device with the given serial.
*
* @param deviceSerial deviceSerial The device serial. If null, the first connected device will be used.
*/
internal constructor(deviceSerial: String?) {
device = Utils.getDevice(deviceSerial, logger)
}
override fun runCommand(command: String) = device.shellProcessBuilder(command).start().let { process ->
object : RunResult {
override val exitCode by lazy { process.waitFor() }
override val output by lazy { process.inputStream.bufferedReader().readText() }
override val error by lazy { process.errorStream.bufferedReader().readText() }
override fun waitFor() {
process.waitFor()
}
}
}
override fun hasRootPermission(): Boolean = invoke("whoami").exitCode == 0
override fun write(content: InputStream, targetFilePath: String) =
device.push(content, System.currentTimeMillis(), 644, RemoteFile(targetFilePath))
/**
* Moves the given [file] from the local to the target file path on the device.
*
* @param file The file to move.
* @param targetFilePath The target file path.
*/
override fun move(file: File, targetFilePath: String) = device.push(file, RemoteFile(targetFilePath))
}

View File

@@ -0,0 +1,26 @@
package app.revanced.library.installation.command
/**
* The result of a command execution.
*/
interface RunResult {
/**
* The exit code of the command.
*/
val exitCode: Int
/**
* The output of the command.
*/
val output: String
/**
* The error of the command.
*/
val error: String
/**
* Waits for the command to finish.
*/
fun waitFor() {}
}

View File

@@ -0,0 +1,59 @@
package app.revanced.library.installation.command
import java.io.File
import java.io.InputStream
import java.util.logging.Logger
/**
* [ShellCommandRunner] for running commands on a device.
*/
abstract class ShellCommandRunner internal constructor() {
protected val logger: Logger = Logger.getLogger(this::class.java.name)
/**
* Writes the given [content] to the file at the given [targetFilePath] path.
*
* @param content The content of the file.
* @param targetFilePath The target file path.
*/
internal abstract fun write(
content: InputStream,
targetFilePath: String,
)
/**
* Moves the given [file] to the given [targetFilePath] path.
*
* @param file The file to move.
* @param targetFilePath The target file path.
*/
internal abstract fun move(
file: File,
targetFilePath: String,
)
/**
* Runs the given [command] on the device as root.
*
* @param command The command to run.
* @return The [RunResult].
*/
protected abstract fun runCommand(command: String): RunResult
/**
* Checks if the device has root permission.
*
* @return True if the device has root permission, false otherwise.
*/
internal abstract fun hasRootPermission(): Boolean
/**
* Runs a command on the device as root.
*
* @param command The command to run.
* @return The [RunResult].
*/
internal operator fun invoke(
command: String,
) = runCommand("su -c \'$command\'")
}

View File

@@ -0,0 +1,51 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.command.AdbShellCommandRunner
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
import app.revanced.library.installation.installer.Installer.Apk
import se.vidstige.jadb.JadbException
import se.vidstige.jadb.managers.Package
import se.vidstige.jadb.managers.PackageManager
/**
* [AdbInstaller] for installing and uninstalling [Apk] files using ADB.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*
* @see Installer
*/
class AdbInstaller(
deviceSerial: String? = null,
) : Installer<AdbInstallerResult, Installation>() {
private val device = Utils.getDevice(deviceSerial, logger)
private val adbShellCommandRunner = AdbShellCommandRunner(device)
private val packageManager = PackageManager(device)
init {
logger.fine("Connected to $deviceSerial")
}
override suspend fun install(apk: Apk): AdbInstallerResult {
logger.info("Installing ${apk.file.name}")
return runPackageManager { install(apk.file) }
}
override suspend fun uninstall(packageName: String): AdbInstallerResult {
logger.info("Uninstalling $packageName")
return runPackageManager { uninstall(Package(packageName)) }
}
override suspend fun getInstallation(packageName: String): Installation? = packageManager.packages.find {
it.toString() == packageName
}?.let { Installation(adbShellCommandRunner(INSTALLED_APK_PATH).output) }
private fun runPackageManager(block: PackageManager.() -> Unit) = try {
packageManager.run(block)
AdbInstallerResult.Success
} catch (e: JadbException) {
AdbInstallerResult.Failure(e)
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] via ADB using [AdbInstaller].
*
* @see AdbInstaller
*/
@Suppress("MemberVisibilityCanBePrivate")
interface AdbInstallerResult {
/**
* The result of installing an [Apk] successfully.
*/
object Success : AdbInstallerResult
/**
* The result of installing an [Apk] unsuccessfully.
*
* @param exception The exception that caused the installation to fail.
*/
class Failure internal constructor(val exception: Exception) : AdbInstallerResult
}

View File

@@ -0,0 +1,23 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.command.AdbShellCommandRunner
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
/**
* [AdbMountInstaller] for installing and uninstalling [Apk] files with using ADB root permissions by mounting.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
*
* @throws NoRootPermissionException If the device does not have root permission.
*
* @see MountInstaller
* @see AdbShellCommandRunner
*/
class AdbMountInstaller(
deviceSerial: String? = null,
) : MountInstaller({ AdbShellCommandRunner(deviceSerial) }) {
init {
logger.fine("Connected to $deviceSerial")
}
}

View File

@@ -0,0 +1,75 @@
package app.revanced.library.installation.installer
@Suppress("MemberVisibilityCanBePrivate")
internal object Constants {
const val PLACEHOLDER = "PLACEHOLDER"
const val TMP_FILE_PATH = "/data/local/tmp/revanced.tmp"
const val MOUNT_PATH = "/data/adb/revanced/"
const val MOUNTED_APK_PATH = "$MOUNT_PATH$PLACEHOLDER.apk"
const val MOUNT_SCRIPT_PATH = "/data/adb/service.d/mount_revanced_$PLACEHOLDER.sh"
const val EXISTS = "[[ -f $PLACEHOLDER ]] || exit 1"
const val MOUNT_GREP = "grep $PLACEHOLDER /proc/mounts"
const val DELETE = "rm -rf $PLACEHOLDER"
const val CREATE_DIR = "mkdir -p"
const val RESTART = "am start -S $PLACEHOLDER"
const val KILL = "am force-stop $PLACEHOLDER"
const val INSTALLED_APK_PATH = "pm path $PLACEHOLDER"
const val CREATE_INSTALLATION_PATH = "$CREATE_DIR $MOUNT_PATH"
const val MOUNT_APK =
"base_path=\"$MOUNTED_APK_PATH\" && " +
"mv $TMP_FILE_PATH \$base_path && " +
"chmod 644 \$base_path && " +
"chown system:system \$base_path && " +
"chcon u:object_r:apk_data_file:s0 \$base_path"
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"
const val INSTALL_MOUNT_SCRIPT = "mv $TMP_FILE_PATH $MOUNT_SCRIPT_PATH && chmod +x $MOUNT_SCRIPT_PATH"
val MOUNT_SCRIPT =
"""
#!/system/bin/sh
until [ "$( getprop sys.boot_completed )" = 1 ]; do sleep 3; done
until [ -d "/sdcard/Android" ]; do sleep 1; done
stock_path=$( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
# Make sure the app is installed.
if [ -z "${'$'}stock_path" ]; then
exit 1
fi
# Unmount any existing installations to prevent multiple unnecessary mounts.
$UMOUNT
base_path="$MOUNTED_APK_PATH"
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
# Use Magisk mirror, if possible.
if command -v magisk &> /dev/null; then
MIRROR="${'$'}(magisk --path)/.magisk/mirror"
fi
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
# Kill the app to force it to restart the mounted APK in case it's currently running.
$KILL
""".trimIndent()
/**
* Replaces the [PLACEHOLDER] with the given [replacement].
*
* @param replacement The replacement to use.
* @return The replaced string.
*/
operator fun String.invoke(replacement: String) = replace(PLACEHOLDER, replacement)
}

View File

@@ -0,0 +1,11 @@
package app.revanced.library.installation.installer
/**
* [Installation] of an apk file.
*
* @param apkFilePath The apk file path.
*/
@Suppress("MemberVisibilityCanBePrivate")
open class Installation internal constructor(
val apkFilePath: String,
)

View File

@@ -0,0 +1,53 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
import java.io.File
import java.util.logging.Logger
/**
* [Installer] for installing and uninstalling [Apk] files.
*
* @param TInstallerResult The type of the result of the installation.
* @param TInstallation The type of the installation.
*/
abstract class Installer<TInstallerResult, TInstallation : Installation> internal constructor() {
/**
* The [Logger].
*/
protected val logger: Logger = Logger.getLogger(this::class.java.name)
/**
* Installs the [Apk] file.
*
* @param apk The [Apk] file.
*
* @return The result of the installation.
*/
abstract suspend fun install(apk: Apk): TInstallerResult
/**
* Uninstalls the package.
*
* @param packageName The package name.
*
* @return The result of the uninstallation.
*/
abstract suspend fun uninstall(packageName: String): TInstallerResult
/**
* Gets the current installation or null if not installed.
*
* @param packageName The package name.
*
* @return The installation.
*/
abstract suspend fun getInstallation(packageName: String): TInstallation?
/**
* Apk file for [Installer].
*
* @param file The [Apk] file.
* @param packageName The package name of the [Apk] file.
*/
class Apk(val file: File, val packageName: String? = null)
}

View File

@@ -0,0 +1,15 @@
package app.revanced.library.installation.installer
/**
* [MountInstallation] of the apk file that is mounted to [installedApkFilePath] with root permissions.
*
* @param installedApkFilePath The installed apk file path or null if the apk is not installed.
* @param apkFilePath The mounting apk file path.
* @param mounted Whether the apk is mounted to [installedApkFilePath].
*/
@Suppress("MemberVisibilityCanBePrivate")
class MountInstallation internal constructor(
val installedApkFilePath: String?,
apkFilePath: String,
val mounted: Boolean,
) : Installation(apkFilePath)

View File

@@ -0,0 +1,135 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.command.ShellCommandRunner
import app.revanced.library.installation.installer.Constants.CREATE_INSTALLATION_PATH
import app.revanced.library.installation.installer.Constants.DELETE
import app.revanced.library.installation.installer.Constants.EXISTS
import app.revanced.library.installation.installer.Constants.INSTALLED_APK_PATH
import app.revanced.library.installation.installer.Constants.INSTALL_MOUNT_SCRIPT
import app.revanced.library.installation.installer.Constants.KILL
import app.revanced.library.installation.installer.Constants.MOUNTED_APK_PATH
import app.revanced.library.installation.installer.Constants.MOUNT_APK
import app.revanced.library.installation.installer.Constants.MOUNT_GREP
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT
import app.revanced.library.installation.installer.Constants.MOUNT_SCRIPT_PATH
import app.revanced.library.installation.installer.Constants.RESTART
import app.revanced.library.installation.installer.Constants.TMP_FILE_PATH
import app.revanced.library.installation.installer.Constants.UMOUNT
import app.revanced.library.installation.installer.Constants.invoke
import app.revanced.library.installation.installer.Installer.Apk
import app.revanced.library.installation.installer.MountInstaller.NoRootPermissionException
import java.io.File
/**
* [MountInstaller] for installing and uninstalling [Apk] files using root permissions by mounting.
*
* @param shellCommandRunnerSupplier A supplier for the [ShellCommandRunner] to use.
*
* @throws NoRootPermissionException If the device does not have root permission.
*/
@Suppress("MemberVisibilityCanBePrivate")
abstract class MountInstaller internal constructor(
shellCommandRunnerSupplier: (MountInstaller) -> ShellCommandRunner,
) : Installer<MountInstallerResult, MountInstallation>() {
/**
* The command runner used to run commands on the device.
*/
@Suppress("LeakingThis")
protected val shellCommandRunner = shellCommandRunnerSupplier(this)
init {
if (!shellCommandRunner.hasRootPermission()) throw NoRootPermissionException()
}
/**
* Installs the given [apk] by mounting.
*
* @param apk The [Apk] to install.
*
* @throws PackageNameRequiredException If the [Apk] does not have a package name.
*/
override suspend fun install(apk: Apk): MountInstallerResult {
logger.info("Installing ${apk.packageName} by mounting")
val packageName = apk.packageName?.also { it.assertInstalled() } ?: throw PackageNameRequiredException()
// Setup files.
apk.file.move(TMP_FILE_PATH)
CREATE_INSTALLATION_PATH().waitFor()
MOUNT_APK(packageName)().waitFor()
// Install and run.
TMP_FILE_PATH.write(MOUNT_SCRIPT(packageName))
INSTALL_MOUNT_SCRIPT(packageName)().waitFor()
MOUNT_SCRIPT_PATH(packageName)().waitFor()
RESTART(packageName)()
DELETE(TMP_FILE_PATH)()
return MountInstallerResult.SUCCESS
}
override suspend fun uninstall(packageName: String): MountInstallerResult {
logger.info("Uninstalling $packageName by unmounting")
UMOUNT(packageName)()
DELETE(MOUNTED_APK_PATH)(packageName)()
DELETE(MOUNT_SCRIPT_PATH)(packageName)()
DELETE(TMP_FILE_PATH)() // Remove residual.
KILL(packageName)()
return MountInstallerResult.SUCCESS
}
override suspend fun getInstallation(packageName: String): MountInstallation? {
val patchedApkPath = MOUNTED_APK_PATH(packageName)
val patchedApkExists = EXISTS(patchedApkPath)().exitCode == 0
if (patchedApkExists) return null
return MountInstallation(
INSTALLED_APK_PATH(packageName)().output.ifEmpty { null },
patchedApkPath,
MOUNT_GREP(patchedApkPath)().exitCode == 0,
)
}
/**
* Runs a command on the device.
*/
protected operator fun String.invoke() = shellCommandRunner(this)
/**
* Moves the given file to the given [targetFilePath].
*
* @param targetFilePath The target file path.
*/
protected fun File.move(targetFilePath: String) = shellCommandRunner.move(this, targetFilePath)
/**
* Writes the given [content] to the file.
*
* @param content The content of the file.
*/
protected fun String.write(content: String) = shellCommandRunner.write(content.byteInputStream(), this)
/**
* Asserts that the package is installed.
*
* @throws FailedToFindInstalledPackageException If the package is not installed.
*/
private fun String.assertInstalled() {
if (INSTALLED_APK_PATH(this)().output.isNotEmpty()) {
throw FailedToFindInstalledPackageException(this)
}
}
internal class FailedToFindInstalledPackageException internal constructor(packageName: String) :
Exception("Failed to find installed package \"$packageName\" because no activity was found")
internal class PackageNameRequiredException internal constructor() : Exception("Package name is required")
internal class NoRootPermissionException internal constructor() : Exception("No root permission")
}

View File

@@ -0,0 +1,20 @@
package app.revanced.library.installation.installer
import app.revanced.library.installation.installer.Installer.Apk
/**
* The result of installing or uninstalling an [Apk] with root permissions using [MountInstaller].
*
* @see MountInstaller
*/
enum class MountInstallerResult {
/**
* The result of installing an [Apk] successfully.
*/
SUCCESS,
/**
* The result of installing an [Apk] unsuccessfully.
*/
FAILURE,
}

View File

@@ -0,0 +1,40 @@
package app.revanced.library.installation.installer
import se.vidstige.jadb.JadbConnection
import java.util.logging.Logger
/**
* Utility functions for [Installer].
*
* @see Installer
*/
internal object Utils {
/**
* Gets the device with the given serial.
*
* @param deviceSerial The device serial. If null, the first connected device will be used.
* @param logger The logger.
* @return The device.
* @throws DeviceNotFoundException If no device with the given serial is found.
*/
internal fun getDevice(
deviceSerial: String? = null,
logger: Logger,
) = with(JadbConnection().devices) {
if (isEmpty()) throw DeviceNotFoundException()
deviceSerial?.let {
firstOrNull { it.serial == deviceSerial } ?: throw DeviceNotFoundException(
deviceSerial,
)
} ?: first().also {
logger.warning("No device serial supplied. Using device with serial ${it.serial}")
}
}!!
class DeviceNotFoundException internal constructor(deviceSerial: String? = null) : Exception(
deviceSerial?.let {
"The device with the ADB device serial \"$deviceSerial\" can not be found"
} ?: "No ADB device found",
)
}

View File

@@ -0,0 +1,99 @@
package app.revanced.library.logging
import java.util.logging.Handler
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.SimpleFormatter
@Suppress("MemberVisibilityCanBePrivate", "unused")
object Logger {
/**
* Rules for allowed loggers.
*/
private val allowedLoggersRules =
arrayOf<String.() -> Boolean>(
// ReVanced loggers.
{ startsWith("app.revanced") },
// Logs warnings when compiling resources (Logger in class brut.util.OS).
{ this == "" },
)
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? ->
loggerName?.let { name ->
if (allowedLoggersRules.none { it(name) }) 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)
}
}

View File

@@ -0,0 +1,41 @@
package app.revanced.library
import app.revanced.library.Options.setOptions
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import kotlin.test.Test
class PatchOptionsTest {
private var patches = setOf(PatchOptionsTestPatch)
private val serializedJson =
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\"," +
"\"value\":true}]}]"
private val changedJson =
"[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2" +
"\",\"value\":false}]}]"
@Test
fun `serializes and deserializes`() {
assert(serializedJson == Options.serialize(patches))
patches.setOptions(changedJson)
assert(PatchOptionsTestPatch.option1 == "test")
assert(PatchOptionsTestPatch.option2 == false)
}
@Patch("PatchOptionsTestPatch")
object PatchOptionsTestPatch : BytecodePatch(emptySet()) {
var option1 by stringPatchOption("key1", null, null, "title1", "description1")
var option2 by booleanPatchOption("key2", true, null, "title2", "description2")
override fun execute(context: BytecodeContext) {
// Do nothing
}
}
}

View File

@@ -0,0 +1,199 @@
package app.revanced.library
import app.revanced.patcher.PatchSet
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intArrayPatchOption
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import kotlin.test.Test
import kotlin.test.assertEquals
internal class PatchUtilsTest {
private val patches =
arrayOf(
newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") },
newPatch("some.package", setOf("a", "b"), use = false),
newPatch("some.package", setOf("a", "b", "c"), use = false),
newPatch("some.other.package", setOf("b"), use = false),
newPatch("some.other.package", setOf("b", "c")) { booleanPatchOption("bool", true) },
newPatch("some.other.package", setOf("b", "c", "d")),
newPatch("some.other.other.package") { intArrayPatchOption("intArray", arrayOf(1, 2, 3)) },
newPatch("some.other.other.package", setOf("a")),
newPatch("some.other.other.package", setOf("b")),
newPatch("some.other.other.other.package", use = false),
newPatch("some.other.other.other.package", use = false),
).toSet()
@Test
fun `empty because package is incompatible with any version`() {
assertEqualsVersions(
expected = emptyMap(),
patches = setOf(newPatch("some.package", emptySet(), use = true)),
compatiblePackageNames = setOf("some.package"),
)
}
@Test
fun `empty list of versions because package is unconstrained to any version`() {
assertEqualsVersions(
expected = mapOf("some.package" to linkedMapOf()),
patches = setOf(newPatch("some.package")),
compatiblePackageNames = setOf("some.package"),
countUnusedPatches = true,
)
}
@Test
fun `empty because no known package was supplied`() {
assertEqualsVersions(
expected = emptyMap(),
patches,
compatiblePackageNames = setOf("unknown.package"),
)
}
@Test
fun `common versions correctly ordered for each package`() {
fun assertEqualsExpected(compatiblePackageNames: Set<String>?) =
assertEqualsVersions(
expected =
mapOf(
"some.package" to linkedMapOf("a" to 3, "b" to 2, "c" to 1),
"some.other.package" to linkedMapOf("b" to 3, "c" to 2, "d" to 1),
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
"some.other.other.other.package" to linkedMapOf(),
),
patches,
compatiblePackageNames,
countUnusedPatches = true,
)
assertEqualsExpected(
compatiblePackageNames =
setOf(
"some.package",
"some.other.package",
"some.other.other.package",
"some.other.other.other.package",
),
)
assertEqualsExpected(
compatiblePackageNames = null,
)
}
@Test
fun `common versions correctly ordered for each package without counting unused patches`() {
assertEqualsVersions(
expected =
mapOf(
"some.package" to linkedMapOf("a" to 1),
"some.other.package" to linkedMapOf("b" to 2, "c" to 2, "d" to 1),
"some.other.other.package" to linkedMapOf("a" to 1, "b" to 1),
),
patches,
compatiblePackageNames =
setOf(
"some.package",
"some.other.package",
"some.other.other.package",
"some.other.other.other.package",
),
countUnusedPatches = false,
)
}
@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", setOf(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", setOf("a")))
assertEqualsVersion(null, patches, "other.package")
}
@Test
fun `return null because no compatible package is constrained to a version`() {
val patches =
setOf(
newPatch("other.package"),
newPatch("other.package"),
)
assertEqualsVersion(null, patches, "other.package")
}
@Test
fun `serializes to and deserializes from JSON string correctly`() {
val out = ByteArrayOutputStream()
PatchUtils.Json.serialize(patches, outputStream = out)
val deserialized =
PatchUtils.Json.deserialize(
ByteArrayInputStream(out.toByteArray()),
PatchUtils.Json.FullJsonPatch::class.java,
)
assert(patches.size == deserialized.size)
}
private fun assertEqualsVersions(
expected: PackageNameMap,
patches: PatchSet,
compatiblePackageNames: Set<String>?,
countUnusedPatches: Boolean = false,
) = assertEquals(
expected,
PatchUtils.getMostCommonCompatibleVersions(patches, compatiblePackageNames, countUnusedPatches),
)
private fun assertEqualsVersion(
expected: String?,
patches: PatchSet,
compatiblePackageName: String,
) {
assertEquals(
expected,
PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName))
.entries.firstOrNull()?.value?.keys?.firstOrNull(),
)
}
private fun newPatch(
packageName: String,
versions: Set<String>? = null,
use: Boolean = true,
options: Patch<*>.() -> Unit = {},
) = object : BytecodePatch(
name = "test",
compatiblePackages = setOf(CompatiblePackage(packageName, versions?.toSet())),
use = use,
) {
init {
options()
}
override fun execute(context: BytecodeContext) {}
// Needed to make the patches unique.
override fun equals(other: Any?) = false
}
}