mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-12 06:16:18 +00:00
Compare commits
2 Commits
feat/moder
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39aaf42b2b | ||
|
|
d2cf491ffc |
3
.editorconfig
Normal file
3
.editorconfig
Normal file
@@ -0,0 +1,3 @@
|
||||
[*.{kt,kts}]
|
||||
ktlint_code_style = intellij_idea
|
||||
ktlint_standard_no-wildcard-imports = disabled
|
||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -17,14 +17,18 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v3
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
|
||||
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew build clean
|
||||
|
||||
- name: Setup Node.js
|
||||
@@ -44,7 +48,6 @@ jobs:
|
||||
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Release
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
|
||||
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm exec semantic-release
|
||||
|
||||
133
.gitignore
vendored
133
.gitignore
vendored
@@ -1,19 +1,124 @@
|
||||
### Java template
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
.idea/artifacts
|
||||
.idea/compiler.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/modules.xml
|
||||
.idea/*.iml
|
||||
.idea/modules
|
||||
*.iml
|
||||
.kotlin
|
||||
*.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
.idea/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Gradle template
|
||||
.gradle
|
||||
**/build/
|
||||
xcuserdata
|
||||
!src/**/build/
|
||||
local.properties
|
||||
.idea
|
||||
.DS_Store
|
||||
captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
*.xcodeproj/*
|
||||
!*.xcodeproj/project.pbxproj
|
||||
!*.xcodeproj/xcshareddata/
|
||||
!*.xcodeproj/project.xcworkspace/
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
|
||||
# gradle/wrapper/gradle-wrapper.properties
|
||||
|
||||
# Avoid ignoring test resources
|
||||
!src/test/resources/*
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Gradle props, to avoid sharing the gpr key
|
||||
gradle.properties
|
||||
|
||||
@@ -117,9 +117,9 @@ you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced
|
||||
The documentation contains the fundamentals of ReVanced Patcher and how to use ReVanced Patcher to create patches.
|
||||
You can find it [here](https://github.com/ReVanced/revanced-patcher/tree/main/docs).
|
||||
|
||||
## 📜 Licence
|
||||
## 📜 License
|
||||
|
||||
ReVanced Patcher is licensed under the GPLv3 license. Please see the [licence file](LICENSE) for more information.
|
||||
ReVanced Patcher is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
|
||||
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patcher as long as you track changes/dates in source files.
|
||||
Any modifications to ReVanced Patcher must also be made available under the GPL,
|
||||
along with build & install instructions.
|
||||
|
||||
889
api/revanced-patcher.api
Normal file
889
api/revanced-patcher.api
Normal file
@@ -0,0 +1,889 @@
|
||||
public final class app/revanced/patcher/Fingerprint {
|
||||
public final fun getClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||
public final fun getClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||
public final fun getMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||
public final fun getMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||
public final fun getOriginalClassDef (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public final fun getOriginalClassDefOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public final fun getOriginalMethod (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||
public final fun getOriginalMethodOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||
public final fun getPatternMatch (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
|
||||
public final fun getPatternMatchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Lapp/revanced/patcher/Match$PatternMatch;
|
||||
public final fun getStringMatches (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
|
||||
public final fun getStringMatchesOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;)Ljava/util/List;
|
||||
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
||||
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
|
||||
public final fun match (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
||||
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
||||
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/Match;
|
||||
public final fun matchOrNull (Lapp/revanced/patcher/patch/BytecodePatchContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/Match;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/FingerprintBuilder {
|
||||
public fun <init> ()V
|
||||
public final fun accessFlags (I)V
|
||||
public final fun accessFlags ([Lcom/android/tools/smali/dexlib2/AccessFlags;)V
|
||||
public final fun custom (Lkotlin/jvm/functions/Function2;)V
|
||||
public final fun opcodes (Ljava/lang/String;)V
|
||||
public final fun opcodes ([Lcom/android/tools/smali/dexlib2/Opcode;)V
|
||||
public final fun parameters ([Ljava/lang/String;)V
|
||||
public final fun returns (Ljava/lang/String;)V
|
||||
public final fun strings ([Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/FingerprintKt {
|
||||
public static final fun fingerprint (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/Fingerprint;
|
||||
public static synthetic fun fingerprint$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/Fingerprint;
|
||||
}
|
||||
|
||||
public abstract interface annotation class app/revanced/patcher/InternalApi : java/lang/annotation/Annotation {
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/Match {
|
||||
public final fun getClassDef ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||
public final fun getMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||
public final fun getOriginalClassDef ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public final fun getOriginalMethod ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||
public final fun getPatternMatch ()Lapp/revanced/patcher/Match$PatternMatch;
|
||||
public final fun getStringMatches ()Ljava/util/List;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/Match$PatternMatch {
|
||||
public final fun getEndIndex ()I
|
||||
public final fun getStartIndex ()I
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/Match$StringMatch {
|
||||
public final fun getIndex ()I
|
||||
public final fun getString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PackageMetadata {
|
||||
public final fun getPackageName ()Ljava/lang/String;
|
||||
public final fun getPackageVersion ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/Patcher : java/io/Closeable {
|
||||
public fun <init> (Lapp/revanced/patcher/PatcherConfig;)V
|
||||
public fun close ()V
|
||||
public final fun get ()Lapp/revanced/patcher/PatcherResult;
|
||||
public final fun getContext ()Lapp/revanced/patcher/PatcherContext;
|
||||
public final fun invoke ()Lkotlinx/coroutines/flow/Flow;
|
||||
public final fun plusAssign (Ljava/util/Set;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PatcherConfig {
|
||||
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/io/File;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;)V
|
||||
public synthetic fun <init> (Ljava/io/File;Ljava/io/File;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PatcherContext : java/io/Closeable {
|
||||
public fun close ()V
|
||||
public final fun getPackageMetadata ()Lapp/revanced/patcher/PackageMetadata;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PatcherResult {
|
||||
public final fun getDexFiles ()Ljava/util/Set;
|
||||
public final fun getResources ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PatcherResult$PatchedDexFile {
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getStream ()Ljava/io/InputStream;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/PatcherResult$PatchedResources {
|
||||
public final fun getDeleteResources ()Ljava/util/Set;
|
||||
public final fun getDoNotCompress ()Ljava/util/Set;
|
||||
public final fun getOtherResources ()Ljava/io/File;
|
||||
public final fun getResourcesApk ()Ljava/io/File;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/extensions/ExtensionsKt {
|
||||
public static final fun newLabel (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Lcom/android/tools/smali/dexlib2/builder/Label;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/extensions/InstructionExtensions {
|
||||
public static final field INSTANCE Lapp/revanced/patcher/extensions/InstructionExtensions;
|
||||
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILcom/android/tools/smali/dexlib2/builder/BuilderInstruction;)V
|
||||
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;)V
|
||||
public final fun addInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
|
||||
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/util/List;)V
|
||||
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
|
||||
public final fun addInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/util/List;)V
|
||||
public final fun addInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;ILjava/util/List;)V
|
||||
public final fun addInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;Ljava/util/List;)V
|
||||
public final fun addInstructionsWithLabels (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;[Lapp/revanced/patcher/util/smali/ExternalLabel;)V
|
||||
public final fun getInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||
public final fun getInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Ljava/lang/Object;
|
||||
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;I)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;I)Ljava/lang/Object;
|
||||
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;I)Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;
|
||||
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;I)Ljava/lang/Object;
|
||||
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/iface/MethodImplementation;I)Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;
|
||||
public final fun getInstruction (Lcom/android/tools/smali/dexlib2/iface/MethodImplementation;I)Ljava/lang/Object;
|
||||
public final fun getInstructionOrNull (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||
public final fun getInstructionOrNull (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Ljava/lang/Object;
|
||||
public final fun getInstructionOrNull (Lcom/android/tools/smali/dexlib2/iface/Method;I)Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;
|
||||
public final fun getInstructionOrNull (Lcom/android/tools/smali/dexlib2/iface/Method;I)Ljava/lang/Object;
|
||||
public final fun getInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Ljava/util/List;
|
||||
public final fun getInstructions (Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/lang/Iterable;
|
||||
public final fun getInstructionsOrNull (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Ljava/util/List;
|
||||
public final fun getInstructionsOrNull (Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/lang/Iterable;
|
||||
public final fun removeInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
|
||||
public final fun removeInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
|
||||
public final fun removeInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;II)V
|
||||
public final fun removeInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;I)V
|
||||
public final fun removeInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;II)V
|
||||
public final fun replaceInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILcom/android/tools/smali/dexlib2/builder/BuilderInstruction;)V
|
||||
public final fun replaceInstruction (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||
public final fun replaceInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/String;)V
|
||||
public final fun replaceInstructions (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/util/List;)V
|
||||
public final fun replaceInstructions (Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;ILjava/util/List;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/BytecodePatch : app/revanced/patcher/patch/Patch {
|
||||
public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/BytecodePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
|
||||
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
|
||||
public final fun extendWith (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatchBuilder;
|
||||
public final fun getExtensionInputStream ()Ljava/util/function/Supplier;
|
||||
public final fun setExtensionInputStream (Ljava/util/function/Supplier;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/BytecodePatchContext : app/revanced/patcher/patch/PatchContext, java/io/Closeable {
|
||||
public final fun classBy (Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||
public fun close ()V
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Ljava/util/Set;
|
||||
public final fun getClasses ()Lapp/revanced/patcher/util/ProxyClassList;
|
||||
public final fun navigate (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||
public final fun proxy (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/ClassProxy;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/Option {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Object;Ljava/util/Map;Ljava/lang/String;ZLkotlin/reflect/KType;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun getDefault ()Ljava/lang/Object;
|
||||
public final fun getDescription ()Ljava/lang/String;
|
||||
public final fun getKey ()Ljava/lang/String;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun getRequired ()Z
|
||||
public final fun getTitle ()Ljava/lang/String;
|
||||
public final fun getType ()Lkotlin/reflect/KType;
|
||||
public final fun getValidator ()Lkotlin/jvm/functions/Function2;
|
||||
public final fun getValue ()Ljava/lang/Object;
|
||||
public final fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object;
|
||||
public final fun getValues ()Ljava/util/Map;
|
||||
public final fun reset ()V
|
||||
public final fun setValue (Ljava/lang/Object;)V
|
||||
public final fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patcher/patch/OptionException : java/lang/Exception {
|
||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/OptionException$InvalidValueTypeException : app/revanced/patcher/patch/OptionException {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/OptionException$OptionNotFoundException : app/revanced/patcher/patch/OptionException {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/OptionException$ValueRequiredException : app/revanced/patcher/patch/OptionException {
|
||||
public fun <init> (Lapp/revanced/patcher/patch/Option;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/OptionException$ValueValidationException : app/revanced/patcher/patch/OptionException {
|
||||
public fun <init> (Ljava/lang/Object;Lapp/revanced/patcher/patch/Option;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/OptionKt {
|
||||
public static final fun booleanOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun booleanOption (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun booleanOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun booleanOption$default (Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun booleansOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun booleansOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun booleansOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun booleansOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun floatOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun floatOption (Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun floatOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun floatOption$default (Ljava/lang/String;Ljava/lang/Float;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun floatsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun floatsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun intOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun intOption (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun intOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun intOption$default (Ljava/lang/String;Ljava/lang/Integer;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun intsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun intsOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun intsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun intsOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun longOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun longOption (Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun longOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun longOption$default (Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun longsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun longsOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun longsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun longsOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun stringOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun stringOption (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun stringOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun stringOption$default (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun stringsOption (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static final fun stringsOption (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun stringsOption$default (Lapp/revanced/patcher/patch/PatchBuilder;Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public static synthetic fun stringsOption$default (Ljava/lang/String;Ljava/util/List;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/Options : java/util/Map, kotlin/jvm/internal/markers/KMappedMarker {
|
||||
public fun clear ()V
|
||||
public synthetic fun compute (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
|
||||
public fun compute (Ljava/lang/String;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/Option;
|
||||
public synthetic fun computeIfAbsent (Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;
|
||||
public fun computeIfAbsent (Ljava/lang/String;Ljava/util/function/Function;)Lapp/revanced/patcher/patch/Option;
|
||||
public synthetic fun computeIfPresent (Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
|
||||
public fun computeIfPresent (Ljava/lang/String;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/Option;
|
||||
public final fun containsKey (Ljava/lang/Object;)Z
|
||||
public fun containsKey (Ljava/lang/String;)Z
|
||||
public fun containsValue (Lapp/revanced/patcher/patch/Option;)Z
|
||||
public final fun containsValue (Ljava/lang/Object;)Z
|
||||
public final fun entrySet ()Ljava/util/Set;
|
||||
public final fun get (Ljava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public final synthetic fun get (Ljava/lang/Object;)Ljava/lang/Object;
|
||||
public fun get (Ljava/lang/String;)Lapp/revanced/patcher/patch/Option;
|
||||
public fun getEntries ()Ljava/util/Set;
|
||||
public fun getKeys ()Ljava/util/Set;
|
||||
public fun getSize ()I
|
||||
public fun getValues ()Ljava/util/Collection;
|
||||
public fun isEmpty ()Z
|
||||
public final fun keySet ()Ljava/util/Set;
|
||||
public synthetic fun merge (Ljava/lang/Object;Ljava/lang/Object;Ljava/util/function/BiFunction;)Ljava/lang/Object;
|
||||
public fun merge (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;Ljava/util/function/BiFunction;)Lapp/revanced/patcher/patch/Option;
|
||||
public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
||||
public fun put (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
|
||||
public fun putAll (Ljava/util/Map;)V
|
||||
public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
||||
public fun putIfAbsent (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
|
||||
public fun remove (Ljava/lang/Object;)Lapp/revanced/patcher/patch/Option;
|
||||
public synthetic fun remove (Ljava/lang/Object;)Ljava/lang/Object;
|
||||
public fun remove (Ljava/lang/Object;Ljava/lang/Object;)Z
|
||||
public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
||||
public synthetic fun replace (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Z
|
||||
public fun replace (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
|
||||
public fun replace (Ljava/lang/String;Lapp/revanced/patcher/patch/Option;Lapp/revanced/patcher/patch/Option;)Z
|
||||
public fun replaceAll (Ljava/util/function/BiFunction;)V
|
||||
public final fun set (Ljava/lang/String;Ljava/lang/Object;)V
|
||||
public final fun size ()I
|
||||
public final fun values ()Ljava/util/Collection;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patcher/patch/Patch {
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLjava/util/Set;Ljava/util/Set;Ljava/util/Set;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun execute (Lapp/revanced/patcher/patch/PatchContext;)V
|
||||
public final fun finalize (Lapp/revanced/patcher/patch/PatchContext;)V
|
||||
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 ()Lapp/revanced/patcher/patch/Options;
|
||||
public final fun getUse ()Z
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patcher/patch/PatchBuilder {
|
||||
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun compatibleWith ([Ljava/lang/String;)V
|
||||
public final fun compatibleWith ([Lkotlin/Pair;)V
|
||||
public final fun dependsOn ([Lapp/revanced/patcher/patch/Patch;)V
|
||||
public final fun execute (Lkotlin/jvm/functions/Function1;)V
|
||||
public final fun finalize (Lkotlin/jvm/functions/Function1;)V
|
||||
protected final fun getCompatiblePackages ()Ljava/util/Set;
|
||||
protected final fun getDependencies ()Ljava/util/Set;
|
||||
protected final fun getDescription ()Ljava/lang/String;
|
||||
protected final fun getExecutionBlock ()Lkotlin/jvm/functions/Function1;
|
||||
protected final fun getFinalizeBlock ()Lkotlin/jvm/functions/Function1;
|
||||
protected final fun getName ()Ljava/lang/String;
|
||||
protected final fun getOptions ()Ljava/util/Set;
|
||||
protected final fun getUse ()Z
|
||||
public final fun invoke (Lapp/revanced/patcher/patch/Option;)Lapp/revanced/patcher/patch/Option;
|
||||
public final fun invoke (Ljava/lang/String;[Ljava/lang/String;)Lkotlin/Pair;
|
||||
protected final fun setCompatiblePackages (Ljava/util/Set;)V
|
||||
protected final fun setDependencies (Ljava/util/Set;)V
|
||||
protected final fun setExecutionBlock (Lkotlin/jvm/functions/Function1;)V
|
||||
protected final fun setFinalizeBlock (Lkotlin/jvm/functions/Function1;)V
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/patcher/patch/PatchContext : java/util/function/Supplier {
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/PatchException : java/lang/Exception {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
|
||||
public fun <init> (Ljava/lang/Throwable;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/PatchKt {
|
||||
public static final fun bytecodePatch (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static synthetic fun bytecodePatch$default (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static final fun loadPatchesFromDex (Ljava/util/Set;Ljava/io/File;)Lapp/revanced/patcher/patch/PatchLoader$Dex;
|
||||
public static synthetic fun loadPatchesFromDex$default (Ljava/util/Set;Ljava/io/File;ILjava/lang/Object;)Lapp/revanced/patcher/patch/PatchLoader$Dex;
|
||||
public static final fun loadPatchesFromJar (Ljava/util/Set;)Lapp/revanced/patcher/patch/PatchLoader$Jar;
|
||||
public static final fun rawResourcePatch (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/RawResourcePatch;
|
||||
public static synthetic fun rawResourcePatch$default (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/RawResourcePatch;
|
||||
public static final fun resourcePatch (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
public static synthetic fun resourcePatch$default (Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch;
|
||||
}
|
||||
|
||||
public abstract class app/revanced/patcher/patch/PatchLoader : java/util/Set, kotlin/jvm/internal/markers/KMappedMarker {
|
||||
public synthetic fun <init> (Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public synthetic fun <init> (Ljava/util/Set;Lkotlin/jvm/functions/Function1;Ljava/lang/ClassLoader;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun add (Lapp/revanced/patcher/patch/Patch;)Z
|
||||
public synthetic fun add (Ljava/lang/Object;)Z
|
||||
public fun addAll (Ljava/util/Collection;)Z
|
||||
public fun clear ()V
|
||||
public fun contains (Lapp/revanced/patcher/patch/Patch;)Z
|
||||
public final fun contains (Ljava/lang/Object;)Z
|
||||
public fun containsAll (Ljava/util/Collection;)Z
|
||||
public final fun getByPatchesFile ()Ljava/util/Map;
|
||||
public fun getSize ()I
|
||||
public fun isEmpty ()Z
|
||||
public fun iterator ()Ljava/util/Iterator;
|
||||
public fun remove (Ljava/lang/Object;)Z
|
||||
public fun removeAll (Ljava/util/Collection;)Z
|
||||
public fun retainAll (Ljava/util/Collection;)Z
|
||||
public final fun size ()I
|
||||
public fun toArray ()[Ljava/lang/Object;
|
||||
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/PatchLoader$Dex : app/revanced/patcher/patch/PatchLoader {
|
||||
public fun <init> (Ljava/util/Set;Ljava/io/File;)V
|
||||
public synthetic fun <init> (Ljava/util/Set;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/PatchLoader$Jar : app/revanced/patcher/patch/PatchLoader {
|
||||
public fun <init> (Ljava/util/Set;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/PatchResult {
|
||||
public final fun getException ()Lapp/revanced/patcher/patch/PatchException;
|
||||
public final fun getPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/RawResourcePatch : app/revanced/patcher/patch/Patch {
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/RawResourcePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
|
||||
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/ResourcePatch : app/revanced/patcher/patch/Patch {
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/ResourcePatchBuilder : app/revanced/patcher/patch/PatchBuilder {
|
||||
public synthetic fun build$revanced_patcher ()Lapp/revanced/patcher/patch/Patch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/patch/ResourcePatchContext : app/revanced/patcher/patch/PatchContext {
|
||||
public final fun delete (Ljava/lang/String;)Z
|
||||
public final fun document (Ljava/io/InputStream;)Lapp/revanced/patcher/util/Document;
|
||||
public final fun document (Ljava/lang/String;)Lapp/revanced/patcher/util/Document;
|
||||
public fun get ()Lapp/revanced/patcher/PatcherResult$PatchedResources;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public final fun get (Ljava/lang/String;Z)Ljava/io/File;
|
||||
public static synthetic fun get$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;ZILjava/lang/Object;)Ljava/io/File;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/Document : java/io/Closeable, org/w3c/dom/Document {
|
||||
public fun adoptNode (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||
public fun appendChild (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||
public fun cloneNode (Z)Lorg/w3c/dom/Node;
|
||||
public fun close ()V
|
||||
public fun compareDocumentPosition (Lorg/w3c/dom/Node;)S
|
||||
public fun createAttribute (Ljava/lang/String;)Lorg/w3c/dom/Attr;
|
||||
public fun createAttributeNS (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Attr;
|
||||
public fun createCDATASection (Ljava/lang/String;)Lorg/w3c/dom/CDATASection;
|
||||
public fun createComment (Ljava/lang/String;)Lorg/w3c/dom/Comment;
|
||||
public fun createDocumentFragment ()Lorg/w3c/dom/DocumentFragment;
|
||||
public fun createElement (Ljava/lang/String;)Lorg/w3c/dom/Element;
|
||||
public fun createElementNS (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Element;
|
||||
public fun createEntityReference (Ljava/lang/String;)Lorg/w3c/dom/EntityReference;
|
||||
public fun createProcessingInstruction (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/ProcessingInstruction;
|
||||
public fun createTextNode (Ljava/lang/String;)Lorg/w3c/dom/Text;
|
||||
public fun getAttributes ()Lorg/w3c/dom/NamedNodeMap;
|
||||
public fun getBaseURI ()Ljava/lang/String;
|
||||
public fun getChildNodes ()Lorg/w3c/dom/NodeList;
|
||||
public fun getDoctype ()Lorg/w3c/dom/DocumentType;
|
||||
public fun getDocumentElement ()Lorg/w3c/dom/Element;
|
||||
public fun getDocumentURI ()Ljava/lang/String;
|
||||
public fun getDomConfig ()Lorg/w3c/dom/DOMConfiguration;
|
||||
public fun getElementById (Ljava/lang/String;)Lorg/w3c/dom/Element;
|
||||
public fun getElementsByTagName (Ljava/lang/String;)Lorg/w3c/dom/NodeList;
|
||||
public fun getElementsByTagNameNS (Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/NodeList;
|
||||
public fun getFeature (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
|
||||
public fun getFirstChild ()Lorg/w3c/dom/Node;
|
||||
public fun getImplementation ()Lorg/w3c/dom/DOMImplementation;
|
||||
public fun getInputEncoding ()Ljava/lang/String;
|
||||
public fun getLastChild ()Lorg/w3c/dom/Node;
|
||||
public fun getLocalName ()Ljava/lang/String;
|
||||
public fun getNamespaceURI ()Ljava/lang/String;
|
||||
public fun getNextSibling ()Lorg/w3c/dom/Node;
|
||||
public fun getNodeName ()Ljava/lang/String;
|
||||
public fun getNodeType ()S
|
||||
public fun getNodeValue ()Ljava/lang/String;
|
||||
public fun getOwnerDocument ()Lorg/w3c/dom/Document;
|
||||
public fun getParentNode ()Lorg/w3c/dom/Node;
|
||||
public fun getPrefix ()Ljava/lang/String;
|
||||
public fun getPreviousSibling ()Lorg/w3c/dom/Node;
|
||||
public fun getStrictErrorChecking ()Z
|
||||
public fun getTextContent ()Ljava/lang/String;
|
||||
public fun getUserData (Ljava/lang/String;)Ljava/lang/Object;
|
||||
public fun getXmlEncoding ()Ljava/lang/String;
|
||||
public fun getXmlStandalone ()Z
|
||||
public fun getXmlVersion ()Ljava/lang/String;
|
||||
public fun hasAttributes ()Z
|
||||
public fun hasChildNodes ()Z
|
||||
public fun importNode (Lorg/w3c/dom/Node;Z)Lorg/w3c/dom/Node;
|
||||
public fun insertBefore (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||
public fun isDefaultNamespace (Ljava/lang/String;)Z
|
||||
public fun isEqualNode (Lorg/w3c/dom/Node;)Z
|
||||
public fun isSameNode (Lorg/w3c/dom/Node;)Z
|
||||
public fun isSupported (Ljava/lang/String;Ljava/lang/String;)Z
|
||||
public fun lookupNamespaceURI (Ljava/lang/String;)Ljava/lang/String;
|
||||
public fun lookupPrefix (Ljava/lang/String;)Ljava/lang/String;
|
||||
public fun normalize ()V
|
||||
public fun normalizeDocument ()V
|
||||
public fun removeChild (Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||
public fun renameNode (Lorg/w3c/dom/Node;Ljava/lang/String;Ljava/lang/String;)Lorg/w3c/dom/Node;
|
||||
public fun replaceChild (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)Lorg/w3c/dom/Node;
|
||||
public fun setDocumentURI (Ljava/lang/String;)V
|
||||
public fun setNodeValue (Ljava/lang/String;)V
|
||||
public fun setPrefix (Ljava/lang/String;)V
|
||||
public fun setStrictErrorChecking (Z)V
|
||||
public fun setTextContent (Ljava/lang/String;)V
|
||||
public fun setUserData (Ljava/lang/String;Ljava/lang/Object;Lorg/w3c/dom/UserDataHandler;)Ljava/lang/Object;
|
||||
public fun setXmlStandalone (Z)V
|
||||
public fun setXmlVersion (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/MethodNavigator {
|
||||
public final fun getValue (Ljava/lang/Void;Lkotlin/reflect/KProperty;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||
public final fun original ()Lcom/android/tools/smali/dexlib2/iface/Method;
|
||||
public final fun stop ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||
public final fun to (ILkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||
public final fun to ([I)Lapp/revanced/patcher/util/MethodNavigator;
|
||||
public static synthetic fun to$default (Lapp/revanced/patcher/util/MethodNavigator;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/MethodNavigator;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/ProxyClassList : java/util/List, kotlin/jvm/internal/markers/KMutableList {
|
||||
public fun add (ILcom/android/tools/smali/dexlib2/iface/ClassDef;)V
|
||||
public synthetic fun add (ILjava/lang/Object;)V
|
||||
public fun add (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||
public synthetic fun add (Ljava/lang/Object;)Z
|
||||
public fun addAll (ILjava/util/Collection;)Z
|
||||
public fun addAll (Ljava/util/Collection;)Z
|
||||
public fun clear ()V
|
||||
public fun contains (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||
public final fun contains (Ljava/lang/Object;)Z
|
||||
public fun containsAll (Ljava/util/Collection;)Z
|
||||
public fun get (I)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public synthetic fun get (I)Ljava/lang/Object;
|
||||
public fun getSize ()I
|
||||
public fun indexOf (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)I
|
||||
public final fun indexOf (Ljava/lang/Object;)I
|
||||
public fun isEmpty ()Z
|
||||
public fun iterator ()Ljava/util/Iterator;
|
||||
public fun lastIndexOf (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)I
|
||||
public final fun lastIndexOf (Ljava/lang/Object;)I
|
||||
public fun listIterator ()Ljava/util/ListIterator;
|
||||
public fun listIterator (I)Ljava/util/ListIterator;
|
||||
public final fun remove (I)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public synthetic fun remove (I)Ljava/lang/Object;
|
||||
public fun remove (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
|
||||
public final fun remove (Ljava/lang/Object;)Z
|
||||
public fun removeAll (Ljava/util/Collection;)Z
|
||||
public fun removeAt (I)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public fun retainAll (Ljava/util/Collection;)Z
|
||||
public fun set (ILcom/android/tools/smali/dexlib2/iface/ClassDef;)Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object;
|
||||
public final fun size ()I
|
||||
public fun subList (II)Ljava/util/List;
|
||||
public fun toArray ()[Ljava/lang/Object;
|
||||
public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/ClassProxy {
|
||||
public final fun getImmutableClass ()Lcom/android/tools/smali/dexlib2/iface/ClassDef;
|
||||
public final fun getMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation : com/android/tools/smali/dexlib2/base/BaseAnnotation {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Annotation;)V
|
||||
public fun getElements ()Ljava/util/Set;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public fun getVisibility ()I
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/Annotation;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotation;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement : com/android/tools/smali/dexlib2/base/BaseAnnotationElement {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/AnnotationElement;)V
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/value/EncodedValue;
|
||||
public final fun setName (Ljava/lang/String;)V
|
||||
public final fun setValue (Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/AnnotationElement;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableAnnotationElement;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableClass : com/android/tools/smali/dexlib2/base/reference/BaseTypeReference, com/android/tools/smali/dexlib2/iface/ClassDef {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)V
|
||||
public final fun charAt (I)C
|
||||
public fun get (I)C
|
||||
public fun getAccessFlags ()I
|
||||
public fun getAnnotations ()Ljava/util/Set;
|
||||
public synthetic fun getDirectMethods ()Ljava/lang/Iterable;
|
||||
public fun getDirectMethods ()Ljava/util/Set;
|
||||
public synthetic fun getFields ()Ljava/lang/Iterable;
|
||||
public fun getFields ()Ljava/util/Set;
|
||||
public synthetic fun getInstanceFields ()Ljava/lang/Iterable;
|
||||
public fun getInstanceFields ()Ljava/util/Set;
|
||||
public fun getInterfaces ()Ljava/util/List;
|
||||
public fun getLength ()I
|
||||
public synthetic fun getMethods ()Ljava/lang/Iterable;
|
||||
public fun getMethods ()Ljava/util/Set;
|
||||
public fun getSourceFile ()Ljava/lang/String;
|
||||
public synthetic fun getStaticFields ()Ljava/lang/Iterable;
|
||||
public fun getStaticFields ()Ljava/util/Set;
|
||||
public fun getSuperclass ()Ljava/lang/String;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public synthetic fun getVirtualMethods ()Ljava/lang/Iterable;
|
||||
public fun getVirtualMethods ()Ljava/util/Set;
|
||||
public final fun length ()I
|
||||
public final fun setAccessFlags (I)V
|
||||
public final fun setSourceFile (Ljava/lang/String;)V
|
||||
public final fun setSuperClass (Ljava/lang/String;)V
|
||||
public final fun setType (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableClass$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableField : com/android/tools/smali/dexlib2/base/reference/BaseFieldReference, com/android/tools/smali/dexlib2/iface/Field {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableField$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Field;)V
|
||||
public fun getAccessFlags ()I
|
||||
public fun getAnnotations ()Ljava/util/Set;
|
||||
public fun getDefiningClass ()Ljava/lang/String;
|
||||
public fun getHiddenApiRestrictions ()Ljava/util/Set;
|
||||
public fun getInitialValue ()Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;
|
||||
public synthetic fun getInitialValue ()Lcom/android/tools/smali/dexlib2/iface/value/EncodedValue;
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun setAccessFlags (I)V
|
||||
public final fun setDefiningClass (Ljava/lang/String;)V
|
||||
public final fun setInitialValue (Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;)V
|
||||
public final fun setName (Ljava/lang/String;)V
|
||||
public final fun setType (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableField$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/Field;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableField;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethod : com/android/tools/smali/dexlib2/base/reference/BaseMethodReference, com/android/tools/smali/dexlib2/iface/Method {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/Method;)V
|
||||
public fun getAccessFlags ()I
|
||||
public fun getAnnotations ()Ljava/util/Set;
|
||||
public fun getDefiningClass ()Ljava/lang/String;
|
||||
public fun getHiddenApiRestrictions ()Ljava/util/Set;
|
||||
public fun getImplementation ()Lcom/android/tools/smali/dexlib2/builder/MutableMethodImplementation;
|
||||
public synthetic fun getImplementation ()Lcom/android/tools/smali/dexlib2/iface/MethodImplementation;
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public fun getParameterTypes ()Ljava/util/List;
|
||||
public fun getParameters ()Ljava/util/List;
|
||||
public fun getReturnType ()Ljava/lang/String;
|
||||
public final fun setAccessFlags (I)V
|
||||
public final fun setDefiningClass (Ljava/lang/String;)V
|
||||
public final fun setName (Ljava/lang/String;)V
|
||||
public final fun setReturnType (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethod$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter : com/android/tools/smali/dexlib2/base/BaseMethodParameter, com/android/tools/smali/dexlib2/iface/MethodParameter {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/MethodParameter;)V
|
||||
public final fun charAt (I)C
|
||||
public fun get (I)C
|
||||
public fun getAnnotations ()Ljava/util/Set;
|
||||
public fun getLength ()I
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public fun getSignature ()Ljava/lang/String;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun length ()I
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/MethodParameter;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethodParameter;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseAnnotationEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/AnnotationEncodedValue;)V
|
||||
public fun getElements ()Ljava/util/Set;
|
||||
public fun getType ()Ljava/lang/String;
|
||||
public final fun setType (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/AnnotationEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableAnnotationEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseArrayEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/ArrayEncodedValue;)V
|
||||
public fun getValue ()Ljava/util/List;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ArrayEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableArrayEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseBooleanEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/BooleanEncodedValue;)V
|
||||
public fun getValue ()Z
|
||||
public final fun setValue (Z)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/BooleanEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableBooleanEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseByteEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)V
|
||||
public fun getValue ()B
|
||||
public final fun setValue (B)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseCharEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/CharEncodedValue;)V
|
||||
public fun getValue ()C
|
||||
public final fun setValue (C)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/CharEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableCharEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseDoubleEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/DoubleEncodedValue;)V
|
||||
public fun getValue ()D
|
||||
public final fun setValue (D)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/DoubleEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableDoubleEncodedValue;
|
||||
}
|
||||
|
||||
public abstract interface class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue : com/android/tools/smali/dexlib2/iface/value/EncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue$Companion;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/EncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseEnumEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/EnumEncodedValue;)V
|
||||
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;
|
||||
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/EnumEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEnumEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseFieldEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/FieldEncodedValue;)V
|
||||
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;
|
||||
public fun getValueType ()I
|
||||
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/FieldReference;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/FieldEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFieldEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseFloatEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/FloatEncodedValue;)V
|
||||
public fun getValue ()F
|
||||
public final fun setValue (F)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/FloatEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableFloatEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseIntEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/IntEncodedValue;)V
|
||||
public fun getValue ()I
|
||||
public final fun setValue (I)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/IntEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableIntEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseLongEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/LongEncodedValue;)V
|
||||
public fun getValue ()J
|
||||
public final fun setValue (J)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/LongEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableLongEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseMethodEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/MethodEncodedValue;)V
|
||||
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;
|
||||
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/MethodReference;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/MethodEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseMethodHandleEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/MethodHandleEncodedValue;)V
|
||||
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodHandleReference;
|
||||
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/MethodHandleReference;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/MethodHandleEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodHandleEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseMethodTypeEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/MethodTypeEncodedValue;)V
|
||||
public fun getValue ()Lcom/android/tools/smali/dexlib2/iface/reference/MethodProtoReference;
|
||||
public final fun setValue (Lcom/android/tools/smali/dexlib2/iface/reference/MethodProtoReference;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/MethodTypeEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableMethodTypeEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableNullEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseNullEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableNullEncodedValue$Companion;
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableNullEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseShortEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/ShortEncodedValue;)V
|
||||
public fun getValue ()S
|
||||
public final fun setValue (S)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ShortEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableShortEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableStringEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseStringEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableStringEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/StringEncodedValue;)V
|
||||
public fun getValue ()Ljava/lang/String;
|
||||
public final fun setValue (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableStringEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/ByteEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableByteEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue : com/android/tools/smali/dexlib2/base/value/BaseTypeEncodedValue, app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableEncodedValue {
|
||||
public static final field Companion Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue$Companion;
|
||||
public fun <init> (Lcom/android/tools/smali/dexlib2/iface/value/TypeEncodedValue;)V
|
||||
public fun getValue ()Ljava/lang/String;
|
||||
public final fun setValue (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue$Companion {
|
||||
public final fun toMutable (Lcom/android/tools/smali/dexlib2/iface/value/TypeEncodedValue;)Lapp/revanced/patcher/util/proxy/mutableTypes/encodedValue/MutableTypeEncodedValue;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/smali/ExternalLabel {
|
||||
public fun <init> (Ljava/lang/String;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;)V
|
||||
public final fun copy (Ljava/lang/String;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;)Lapp/revanced/patcher/util/smali/ExternalLabel;
|
||||
public static synthetic fun copy$default (Lapp/revanced/patcher/util/smali/ExternalLabel;Ljava/lang/String;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;ILjava/lang/Object;)Lapp/revanced/patcher/util/smali/ExternalLabel;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/smali/InlineSmaliCompiler {
|
||||
public static final field Companion Lapp/revanced/patcher/util/smali/InlineSmaliCompiler$Companion;
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/smali/InlineSmaliCompiler$Companion {
|
||||
public final fun compile (Ljava/lang/String;Ljava/lang/String;IZ)Ljava/util/List;
|
||||
}
|
||||
|
||||
public final class app/revanced/patcher/util/smali/InlineSmaliCompilerKt {
|
||||
public static final fun toInstruction (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||
public static synthetic fun toInstruction$default (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/Object;)Lcom/android/tools/smali/dexlib2/builder/BuilderInstruction;
|
||||
public static final fun toInstructions (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Ljava/util/List;
|
||||
public static synthetic fun toInstructions$default (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ILjava/lang/Object;)Ljava/util/List;
|
||||
}
|
||||
|
||||
122
build.gradle.kts
122
build.gradle.kts
@@ -1,5 +1,119 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.kotlin.multiplatform.library) apply false
|
||||
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||
alias(libs.plugins.vanniktech.mavenPublish) apply false
|
||||
}
|
||||
alias(libs.plugins.kotlin)
|
||||
alias(libs.plugins.binary.compatibility.validator)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
tasks {
|
||||
processResources {
|
||||
expand("projectVersion" to project.version)
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events("PASSED", "SKIPPED", "FAILED")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven {
|
||||
// A repository must be specified for some reason. "registry" is a dummy.
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// TODO: Convert project to KMP.
|
||||
compileOnly(libs.android) {
|
||||
// Exclude, otherwise the org.w3c.dom API breaks.
|
||||
exclude(group = "xerces", module = "xmlParserAPIs")
|
||||
}
|
||||
|
||||
implementation(libs.apktool.lib)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.multidexlib2)
|
||||
implementation(libs.smali)
|
||||
implementation(libs.xpp3)
|
||||
|
||||
testImplementation(libs.mockk)
|
||||
testImplementation(libs.kotlin.test)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
|
||||
freeCompilerArgs = listOf("-Xcontext-receivers")
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
create<MavenPublication>("revanced-patcher-publication") {
|
||||
from(components["java"])
|
||||
|
||||
version = project.version.toString()
|
||||
|
||||
pom {
|
||||
name = "ReVanced Patcher"
|
||||
description = "Patcher used by 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-patcher.git"
|
||||
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
|
||||
url = "https://github.com/revanced/revanced-patcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(publishing.publications["revanced-patcher-publication"])
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
version = 22.0.0-local
|
||||
#Kotlin
|
||||
kotlin.code.style=official
|
||||
kotlin.daemon.jvmargs=-Xmx3072M
|
||||
#Gradle
|
||||
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
#Android
|
||||
android.nonTransitiveRClass=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
version = 21.1.0-dev.5
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
|
||||
[versions]
|
||||
agp = "8.12.3"
|
||||
android-compileSdk = "36"
|
||||
android-minSdk = "26"
|
||||
kotlin = "2.3.0"
|
||||
android = "4.1.1.4"
|
||||
apktool-lib = "2.10.1.1"
|
||||
mockk = "1.14.7"
|
||||
binary-compatibility-validator = "0.18.1"
|
||||
kotlin = "2.0.20"
|
||||
kotlinx-coroutines-core = "1.10.2"
|
||||
mockk = "1.14.5"
|
||||
multidexlib2 = "3.0.3.r3"
|
||||
# Tracking https://github.com/google/smali/issues/64.
|
||||
#noinspection GradleDependency
|
||||
smali = "3.0.9"
|
||||
xpp3 = "1.1.4c"
|
||||
vanniktechMavenPublish = "0.35.0"
|
||||
|
||||
[libraries]
|
||||
android = { module = "com.google.android:android", version.ref = "android" }
|
||||
apktool-lib = { module = "app.revanced:apktool-lib", version.ref = "apktool-lib" }
|
||||
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
|
||||
@@ -23,6 +23,5 @@ smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
||||
xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" }
|
||||
|
||||
[plugins]
|
||||
android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktechMavenPublish" }
|
||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
|
||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
12
gradlew
vendored
12
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015 the original authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -86,7 +86,8 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -114,6 +115,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -171,6 +173,7 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -203,14 +206,15 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
3
gradlew.bat
vendored
3
gradlew.bat
vendored
@@ -70,10 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,110 +0,0 @@
|
||||
import com.android.build.api.dsl.androidLibrary
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.android.kotlin.multiplatform.library)
|
||||
alias(libs.plugins.vanniktech.mavenPublish)
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
kotlin {
|
||||
@OptIn(ExperimentalAbiValidation::class)
|
||||
abiValidation {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
jvm()
|
||||
|
||||
androidLibrary {
|
||||
namespace = "app.revanced.patcher"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
|
||||
withHostTestBuilder {}.configure {}
|
||||
withDeviceTestBuilder {
|
||||
sourceSetTreeName = "test"
|
||||
}
|
||||
|
||||
compilations.configureEach {
|
||||
compilerOptions.configure {
|
||||
jvmTarget.set(
|
||||
JvmTarget.JVM_11
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain.dependencies {
|
||||
implementation(libs.apktool.lib)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.multidexlib2)
|
||||
implementation(libs.smali)
|
||||
implementation(libs.xpp3)
|
||||
}
|
||||
|
||||
jvmTest.dependencies {
|
||||
implementation(libs.mockk)
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll(
|
||||
"-Xexplicit-backing-fields",
|
||||
"-Xcontext-parameters"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
named<Test>("jvmTest") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "githubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signAllPublications()
|
||||
extensions.getByType<SigningExtension>().useGpgCmd()
|
||||
|
||||
coordinates(group.toString(), project.name, version.toString())
|
||||
|
||||
pom {
|
||||
name = "ReVanced Patcher"
|
||||
description = "Patcher used by ReVanced."
|
||||
inceptionYear = "2022"
|
||||
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-patcher.git"
|
||||
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
|
||||
url = "https://github.com/revanced/revanced-patcher"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import dalvik.system.DexClassLoader
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import java.io.File
|
||||
|
||||
actual val Class<*>.isPatch get() = Patch::class.java.isAssignableFrom(this)
|
||||
|
||||
/**
|
||||
* Loads patches from DEX files declared as public static fields
|
||||
* or returned by public static and non-parametrized methods.
|
||||
* Patches with no name are not loaded. If a patches file fails to load,
|
||||
* the [onFailedToLoad] callback is invoked with the file and the throwable
|
||||
* and the loading continues for the other files.
|
||||
*
|
||||
* @param patchesFiles The DEX files to load the patches from.
|
||||
* @param onFailedToLoad A callback invoked when a patches file fails to load.
|
||||
*
|
||||
* @return The loaded patches.
|
||||
*/
|
||||
actual fun loadPatches(
|
||||
vararg patchesFiles: File,
|
||||
onFailedToLoad: (patchesFile: File, throwable: Throwable) -> Unit,
|
||||
) = loadPatches(
|
||||
patchesFiles = patchesFiles,
|
||||
{ patchBundle ->
|
||||
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
|
||||
.map { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1)
|
||||
}
|
||||
},
|
||||
DexClassLoader(
|
||||
patchesFiles.joinToString(File.pathSeparator) { it.absolutePath },
|
||||
null,
|
||||
null, null
|
||||
),
|
||||
onFailedToLoad
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
package collections
|
||||
|
||||
actual fun <K, V> MutableMap<K, V>.kmpMerge(
|
||||
key: K,
|
||||
value: V,
|
||||
remappingFunction: (oldValue: V, newValue: V) -> V
|
||||
) = merge(key, value, remappingFunction)
|
||||
@@ -1,8 +0,0 @@
|
||||
package java.io
|
||||
|
||||
import java.nio.charset.Charset
|
||||
|
||||
internal actual fun File.kmpResolve(child: String) = resolve(child)
|
||||
internal actual fun File.kmpDeleteRecursively() = deleteRecursively()
|
||||
internal actual fun File.kmpInputStream() = inputStream()
|
||||
internal actual fun File.kmpBufferedWriter(charset: Charset) = bufferedWriter(charset)
|
||||
@@ -1,363 +0,0 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.extensions.instructionsOrNull
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@Deprecated("Use the matcher API instead.")
|
||||
class Fingerprint internal constructor(
|
||||
internal val accessFlags: Int?,
|
||||
internal val returnType: String?,
|
||||
internal val parameters: List<String>?,
|
||||
internal val opcodes: List<Opcode?>?,
|
||||
internal val strings: List<String>?,
|
||||
internal val custom: ((method: Method, classDef: ClassDef) -> Boolean)?,
|
||||
private val fuzzyPatternScanThreshold: Int,
|
||||
) {
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
// Backing field needed for lazy initialization.
|
||||
private var _matchOrNull: FingerprintMatch? = null
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
private val matchOrNull: FingerprintMatch?
|
||||
get() = matchOrNull()
|
||||
|
||||
context(context: BytecodePatchContext)
|
||||
internal fun matchOrNull(): FingerprintMatch? {
|
||||
if (_matchOrNull != null) return _matchOrNull
|
||||
|
||||
var match = strings?.mapNotNull {
|
||||
context.classDefs.methodsByString[it]
|
||||
}?.minByOrNull { it.size }?.let { methodClasses ->
|
||||
methodClasses.forEach { method ->
|
||||
val match = matchOrNull(method, context.classDefs[method.definingClass]!!)
|
||||
if (match != null) return@let match
|
||||
}
|
||||
|
||||
null
|
||||
}
|
||||
if (match != null) return match
|
||||
context.classDefs.forEach { classDef ->
|
||||
match = matchOrNull(classDef)
|
||||
if (match != null) return match
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
fun matchOrNull(
|
||||
classDef: ClassDef,
|
||||
): FingerprintMatch? {
|
||||
if (_matchOrNull != null) return _matchOrNull
|
||||
|
||||
for (method in classDef.methods) {
|
||||
val match = matchOrNull(method, classDef)
|
||||
if (match != null) return match
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
context(context: BytecodePatchContext)
|
||||
fun matchOrNull(
|
||||
method: Method,
|
||||
) = matchOrNull(method, context.classDefs[method.definingClass]!!)
|
||||
|
||||
context(context: BytecodePatchContext)
|
||||
fun matchOrNull(
|
||||
method: Method,
|
||||
classDef: ClassDef,
|
||||
): FingerprintMatch? {
|
||||
if (_matchOrNull != null) return _matchOrNull
|
||||
|
||||
if (returnType != null && !method.returnType.startsWith(returnType)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (accessFlags != null && accessFlags != method.accessFlags) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>,
|
||||
parameters2: Iterable<CharSequence>,
|
||||
): Boolean {
|
||||
if (parameters1.count() != parameters2.count()) return false
|
||||
val iterator1 = parameters1.iterator()
|
||||
parameters2.forEach {
|
||||
if (!it.startsWith(iterator1.next())) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: parseParameters()
|
||||
if (parameters != null && !parametersEqual(parameters, method.parameterTypes)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (custom != null && !custom.invoke(method, classDef)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val stringMatches: List<FingerprintMatch.StringMatch>? =
|
||||
if (strings != null) {
|
||||
buildList {
|
||||
val instructions = method.instructionsOrNull ?: return null
|
||||
|
||||
val stringsList = strings.toMutableList()
|
||||
|
||||
instructions.forEachIndexed { instructionIndex, instruction ->
|
||||
if (
|
||||
instruction.opcode != Opcode.CONST_STRING &&
|
||||
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||
) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst(string::contains)
|
||||
if (index == -1) return@forEachIndexed
|
||||
|
||||
add(FingerprintMatch.StringMatch(string, instructionIndex))
|
||||
stringsList.removeAt(index)
|
||||
}
|
||||
|
||||
if (stringsList.isNotEmpty()) return null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val patternMatch = if (opcodes != null) {
|
||||
val instructions = method.instructionsOrNull ?: return null
|
||||
|
||||
fun patternScan(): FingerprintMatch.PatternMatch? {
|
||||
val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold
|
||||
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = opcodes.size
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = opcodes.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// Reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions.
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// If the entire pattern has not been scanned yet, continue the scan.
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
|
||||
// The entire pattern has been scanned.
|
||||
return FingerprintMatch.PatternMatch(
|
||||
index,
|
||||
index + patternIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
patternScan() ?: return null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
_matchOrNull = FingerprintMatch(
|
||||
context,
|
||||
classDef,
|
||||
method,
|
||||
patternMatch,
|
||||
stringMatches,
|
||||
)
|
||||
|
||||
return _matchOrNull
|
||||
}
|
||||
|
||||
private val exception get() = PatchException("Failed to match the fingerprint: $this")
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
private val match
|
||||
get() = matchOrNull ?: throw exception
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
fun match(
|
||||
classDef: ClassDef,
|
||||
) = matchOrNull(classDef) ?: throw exception
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
fun match(
|
||||
method: Method,
|
||||
) = matchOrNull(method) ?: throw exception
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
fun match(
|
||||
method: Method,
|
||||
classDef: ClassDef,
|
||||
) = matchOrNull(method, classDef) ?: throw exception
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val originalClassDefOrNull
|
||||
get() = matchOrNull?.originalClassDef
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val originalMethodOrNull
|
||||
get() = matchOrNull?.originalMethod
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val classDefOrNull
|
||||
get() = matchOrNull?.classDef
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val methodOrNull
|
||||
get() = matchOrNull?.method
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val patternMatchOrNull
|
||||
get() = matchOrNull?.patternMatch
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val stringMatchesOrNull
|
||||
get() = matchOrNull?.stringMatches
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val originalClassDef
|
||||
get() = match.originalClassDef
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val originalMethod
|
||||
get() = match.originalMethod
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val classDef
|
||||
get() = match.classDef
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val method
|
||||
get() = match.method
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val patternMatch
|
||||
get() = match.patternMatch
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
val stringMatches
|
||||
get() = match.stringMatches
|
||||
}
|
||||
|
||||
@Deprecated("Use the matcher API instead.")
|
||||
class FingerprintMatch internal constructor(
|
||||
val context: BytecodePatchContext,
|
||||
val originalClassDef: ClassDef,
|
||||
val originalMethod: Method,
|
||||
val patternMatch: PatternMatch?,
|
||||
val stringMatches: List<StringMatch>?,
|
||||
) {
|
||||
|
||||
val classDef by lazy {
|
||||
val classDef = context.classDefs[originalClassDef.type]!!
|
||||
|
||||
context.classDefs.getOrReplaceMutable(classDef)
|
||||
}
|
||||
|
||||
val method by lazy { classDef.methods.first { MethodUtil.methodSignaturesMatch(it, originalMethod) } }
|
||||
|
||||
class PatternMatch internal constructor(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
)
|
||||
|
||||
class StringMatch internal constructor(val string: String, val index: Int)
|
||||
}
|
||||
|
||||
@Deprecated("Use the matcher API instead.")
|
||||
class FingerprintBuilder internal constructor(
|
||||
private val fuzzyPatternScanThreshold: Int = 0,
|
||||
) {
|
||||
private var accessFlags: Int? = null
|
||||
private var returnType: String? = null
|
||||
private var parameters: List<String>? = null
|
||||
private var opcodes: List<Opcode?>? = null
|
||||
private var strings: List<String>? = null
|
||||
private var customBlock: ((method: Method, classDef: ClassDef) -> Boolean)? = null
|
||||
|
||||
fun accessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
fun accessFlags(vararg accessFlags: AccessFlags) {
|
||||
this.accessFlags = accessFlags.fold(0) { acc, it -> acc or it.value }
|
||||
}
|
||||
|
||||
fun returns(returnType: String) {
|
||||
this.returnType = returnType
|
||||
}
|
||||
|
||||
fun parameters(vararg parameters: String) {
|
||||
this.parameters = parameters.toList()
|
||||
}
|
||||
|
||||
fun opcodes(vararg opcodes: Opcode?) {
|
||||
this.opcodes = opcodes.toList()
|
||||
}
|
||||
|
||||
fun opcodes(instructions: String) {
|
||||
this.opcodes = instructions.trimIndent().split("\n").filter {
|
||||
it.isNotBlank()
|
||||
}.map {
|
||||
// Remove any operands.
|
||||
val name = it.split(" ", limit = 1).first().trim()
|
||||
if (name == "null") return@map null
|
||||
|
||||
opcodesByName[name] ?: throw Exception("Unknown opcode: $name")
|
||||
}
|
||||
}
|
||||
|
||||
fun strings(vararg strings: String) {
|
||||
this.strings = strings.toList()
|
||||
}
|
||||
|
||||
fun custom(customBlock: (method: Method, classDef: ClassDef) -> Boolean) {
|
||||
this.customBlock = customBlock
|
||||
}
|
||||
|
||||
internal fun build() = Fingerprint(
|
||||
accessFlags,
|
||||
returnType,
|
||||
parameters,
|
||||
opcodes,
|
||||
strings,
|
||||
customBlock,
|
||||
fuzzyPatternScanThreshold,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val opcodesByName = Opcode.entries.associateBy { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use the matcher API instead.")
|
||||
fun fingerprint(
|
||||
fuzzyPatternScanThreshold: Int = 0,
|
||||
block: FingerprintBuilder.() -> Unit,
|
||||
) = FingerprintBuilder(fuzzyPatternScanThreshold).apply(block).build()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,152 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.deleteRecursively
|
||||
import java.io.resolve
|
||||
import java.util.logging.Logger
|
||||
|
||||
fun patcher(
|
||||
apkFile: File,
|
||||
temporaryFilesPath: File = File("revanced-patcher-temporary-files"),
|
||||
aaptBinaryPath: File? = null,
|
||||
frameworkFileDirectory: String? = null,
|
||||
getPatches: (packageName: String, versionName: String) -> Set<Patch>,
|
||||
): (emit: (PatchResult) -> Unit) -> PatchesResult {
|
||||
val logger = Logger.getLogger("Patcher")
|
||||
|
||||
if (temporaryFilesPath.exists()) {
|
||||
logger.info("Deleting existing temporary files directory")
|
||||
|
||||
if (!temporaryFilesPath.deleteRecursively())
|
||||
logger.severe("Failed to delete existing temporary files directory")
|
||||
}
|
||||
|
||||
val apkFilesPath = temporaryFilesPath.resolve("apk").also { it.mkdirs() }
|
||||
val patchedFilesPath = temporaryFilesPath.resolve("patched").also { it.mkdirs() }
|
||||
|
||||
val resourcePatchContext = ResourcePatchContext(
|
||||
apkFile,
|
||||
apkFilesPath,
|
||||
patchedFilesPath,
|
||||
aaptBinaryPath,
|
||||
frameworkFileDirectory
|
||||
)
|
||||
|
||||
val (packageName, versionName) = resourcePatchContext.decodeManifest()
|
||||
val patches = getPatches(packageName, versionName)
|
||||
|
||||
return { emit: (PatchResult) -> Unit ->
|
||||
if (patches.any { patch -> patch.patchesResources }) resourcePatchContext.decodeResources()
|
||||
|
||||
// After initializing the resource context, to keep memory usage time low.
|
||||
val bytecodePatchContext = BytecodePatchContext(
|
||||
apkFile,
|
||||
patchedFilesPath
|
||||
)
|
||||
|
||||
logger.info("Warming up the cache")
|
||||
|
||||
bytecodePatchContext.classDefs.initializeCache()
|
||||
|
||||
logger.info("Applying patches")
|
||||
|
||||
patches.apply(bytecodePatchContext, resourcePatchContext, emit)
|
||||
}
|
||||
}
|
||||
|
||||
// Public for testing.
|
||||
fun Set<Patch>.apply(
|
||||
bytecodePatchContext: BytecodePatchContext,
|
||||
resourcePatchContext: ResourcePatchContext,
|
||||
emit: (PatchResult) -> Unit
|
||||
): PatchesResult {
|
||||
val appliedPatches = LinkedHashMap<Patch, PatchResult>()
|
||||
|
||||
sortedBy { it.name }.forEach { patch ->
|
||||
fun Patch.apply(): PatchResult {
|
||||
val result = appliedPatches[this]
|
||||
|
||||
return if (result == null) {
|
||||
val failedDependency = dependencies.asSequence().map { it.apply() }.firstOrNull { it.exception != null }
|
||||
if (failedDependency != null) return patchResult(
|
||||
"The dependant patch \"$failedDependency\" of the patch \"$this\" raised an exception:\n" +
|
||||
failedDependency.exception!!.stackTraceToString(),
|
||||
)
|
||||
|
||||
val exception = runCatching { apply(bytecodePatchContext, resourcePatchContext) }
|
||||
.exceptionOrNull() as? Exception
|
||||
|
||||
patchResult(exception).also { result -> appliedPatches[this] = result }
|
||||
} else if (result.exception == null) result
|
||||
else patchResult("The patch '$this' has failed previously")
|
||||
}
|
||||
|
||||
val patchResult = patch.apply()
|
||||
|
||||
// If an exception occurred or the patch has no finalize block, emit the result.
|
||||
if (patchResult.exception != null || patch.afterDependents == null) {
|
||||
emit(patchResult)
|
||||
}
|
||||
}
|
||||
|
||||
val succeededPatchesWithFinalizeBlock = appliedPatches.values.filter {
|
||||
it.exception == null && it.patch.afterDependents != null
|
||||
}
|
||||
|
||||
succeededPatchesWithFinalizeBlock.asReversed().forEach { result ->
|
||||
val patch = result.patch
|
||||
runCatching { patch.afterDependents!!.invoke(bytecodePatchContext, resourcePatchContext) }.fold(
|
||||
{ emit(result) },
|
||||
{
|
||||
emit(
|
||||
PatchResult(
|
||||
patch,
|
||||
PatchException(
|
||||
"The patch \"$patch\" raised an exception:\n" + it.stackTraceToString(),
|
||||
it,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return PatchesResult(bytecodePatchContext.get(), resourcePatchContext.get())
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of applying patches.
|
||||
*
|
||||
* @param dexFiles The patched dex files.
|
||||
* @param resources The patched resources.
|
||||
*/
|
||||
class PatchesResult internal constructor(
|
||||
val dexFiles: Set<PatchedDexFile>,
|
||||
val resources: PatchedResources?,
|
||||
) {
|
||||
|
||||
/**
|
||||
* A dex file.
|
||||
*
|
||||
* @param name The original name of the dex file.
|
||||
* @param stream The dex file as [InputStream].
|
||||
*/
|
||||
class PatchedDexFile internal constructor(val name: String, val stream: InputStream)
|
||||
|
||||
/**
|
||||
* The resources of a patched apk.
|
||||
*
|
||||
* @param resourcesApk The compiled resources.apk file.
|
||||
* @param otherResources The directory containing other resources files.
|
||||
* @param doNotCompress List of files that should not be compressed.
|
||||
* @param deleteResources List of resources that should be deleted.
|
||||
*/
|
||||
class PatchedResources internal constructor(
|
||||
val resourcesApk: File?,
|
||||
val otherResources: File?,
|
||||
val doNotCompress: Set<String>,
|
||||
val deleteResources: Set<String>,
|
||||
)
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.*
|
||||
import com.android.tools.smali.dexlib2.writer.builder.DexBuilder
|
||||
import com.android.tools.smali.smali.smaliFlexLexer
|
||||
import com.android.tools.smali.smali.smaliParser
|
||||
import com.android.tools.smali.smali.smaliTreeWalker
|
||||
import org.antlr.runtime.CommonTokenStream
|
||||
import org.antlr.runtime.TokenSource
|
||||
import org.antlr.runtime.tree.CommonTreeNodeStream
|
||||
import java.io.StringReader
|
||||
|
||||
|
||||
private inline fun <reified T : Reference> Instruction.reference(): T? =
|
||||
(this as? ReferenceInstruction)?.reference as? T
|
||||
|
||||
val Instruction.reference: Reference?
|
||||
get() = reference()
|
||||
|
||||
val Instruction.methodReference
|
||||
get() = reference<MethodReference>()
|
||||
|
||||
val Instruction.fieldReference
|
||||
get() = reference<FieldReference>()
|
||||
|
||||
val Instruction.typeReference
|
||||
get() = reference<TypeReference>()
|
||||
|
||||
val Instruction.stringReference
|
||||
get() = reference<StringReference>()
|
||||
|
||||
/** TODO: This is technically missing for consistency:
|
||||
|
||||
private inline fun <reified T : Reference> Instruction.reference2(): T? =
|
||||
(this as? DualReferenceInstruction)?.reference2 as? T
|
||||
|
||||
val Instruction.reference2: Reference?
|
||||
get() = reference2()
|
||||
|
||||
val Instruction.methodReference2
|
||||
get() = reference2<MethodReference>()
|
||||
|
||||
val Instruction.fieldReference2
|
||||
get() = reference2<FieldReference>()
|
||||
|
||||
val Instruction.typeReference2
|
||||
get() = reference2<TypeReference>()
|
||||
|
||||
val Instruction.stringReference2
|
||||
get() = reference2<StringReference>()
|
||||
**/
|
||||
|
||||
val Instruction.type
|
||||
get() = typeReference?.type
|
||||
|
||||
val Instruction.string
|
||||
get() = stringReference?.string
|
||||
val Instruction.wideLiteral
|
||||
get() = (this as? NarrowLiteralInstruction)?.wideLiteral
|
||||
|
||||
|
||||
private const val CLASS_HEADER = ".class LInlineCompiler;\n.super Ljava/lang/Object;\n"
|
||||
private const val STATIC_HEADER = "$CLASS_HEADER.method public static dummyMethod("
|
||||
private const val HEADER = "$CLASS_HEADER.method public dummyMethod("
|
||||
private val sb by lazy { StringBuilder(512) }
|
||||
|
||||
/**
|
||||
* Compile lines of Smali code to a list of instructions.
|
||||
*
|
||||
* Note: Adding compiled instructions to an existing method with
|
||||
* offset instructions WITHOUT specifying a parent method will not work.
|
||||
* @param templateMethod The method to compile the instructions against.
|
||||
* @returns A list of instructions.
|
||||
*/
|
||||
fun String.toInstructions(templateMethod: com.android.tools.smali.dexlib2.mutable.MutableMethod? = null): List<BuilderInstruction> {
|
||||
val parameters = templateMethod?.parameterTypes?.joinToString("") { it } ?: ""
|
||||
val registers = templateMethod?.implementation?.registerCount ?: 1 // TODO: Should this be 0?
|
||||
val isStatic = templateMethod?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true
|
||||
|
||||
sb.setLength(0) // reset
|
||||
|
||||
if (isStatic) sb.append(STATIC_HEADER) else sb.append(HEADER)
|
||||
sb.append(parameters).append(")V\n")
|
||||
sb.append(" .registers ").append(registers).append("\n")
|
||||
sb.append(trimIndent()).append("\n")
|
||||
sb.append(".end method")
|
||||
|
||||
val reader = StringReader(sb.toString())
|
||||
val lexer = smaliFlexLexer(reader, 15)
|
||||
val tokens = CommonTokenStream(lexer as TokenSource)
|
||||
val parser = smaliParser(tokens)
|
||||
val fileTree = parser.smali_file()
|
||||
|
||||
if (lexer.numberOfSyntaxErrors > 0 || parser.numberOfSyntaxErrors > 0) {
|
||||
throw IllegalStateException(
|
||||
"Lexer errors: ${lexer.numberOfSyntaxErrors}, Parser errors: ${parser.numberOfSyntaxErrors}"
|
||||
)
|
||||
}
|
||||
|
||||
val treeStream = CommonTreeNodeStream(fileTree.tree).apply {
|
||||
tokenStream = tokens
|
||||
}
|
||||
|
||||
val walker = smaliTreeWalker(treeStream)
|
||||
walker.setDexBuilder(DexBuilder(Opcodes.getDefault()))
|
||||
|
||||
val classDef = walker.smali_file()
|
||||
return classDef.methods.first().instructions.map { it as BuilderInstruction }
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.Label
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.*
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.MethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod
|
||||
|
||||
fun Method.accessFlags(vararg flags: AccessFlags) =
|
||||
accessFlags.and(flags.map { it.ordinal }.reduce { acc, i -> acc or i }) != 0
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = instructions.asReversed().forEach { addInstruction(index, it) }
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
* The instructions will be added at the end of the method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
instructions.forEach { addInstruction(it) }
|
||||
|
||||
/**
|
||||
* Remove instructions from a method at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(
|
||||
index: Int,
|
||||
count: Int,
|
||||
) = repeat(count) {
|
||||
removeInstruction(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the first instructions from a method.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index with the given instructions.
|
||||
* The amount of instructions to replace is the amount of instructions in the given list.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethodImplementation.replaceInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = instructions.forEachIndexed { i, instruction -> replaceInstruction(index + i, instruction) }
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param instruction The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(
|
||||
index: Int,
|
||||
instruction: BuilderInstruction,
|
||||
) = implementation!!.addInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param instruction The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: BuilderInstruction) = implementation!!.addInstruction(instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
) = implementation!!.addInstruction(index, smaliInstructions.toInstructions(this).first())
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(smaliInstructions: String) =
|
||||
implementation!!.addInstruction(smaliInstructions.toInstructions(this).first())
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = implementation!!.addInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
implementation!!.addInstructions(instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
) = implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(smaliInstructions: String) =
|
||||
implementation!!.addInstructions(smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param smaliInstructions The instructions to add.
|
||||
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
|
||||
*/
|
||||
// Special function for adding instructions with external labels.
|
||||
fun MutableMethod.addInstructionsWithLabels(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
vararg externalLabels: ExternalLabel,
|
||||
) {
|
||||
// Create reference dummy instructions for the instructions.
|
||||
val nopSmali =
|
||||
StringBuilder(smaliInstructions).also { builder ->
|
||||
externalLabels.forEach { (name, _) ->
|
||||
builder.append("\n:$name\nnop")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// Compile the instructions with the dummy labels
|
||||
val compiledInstructions = nopSmali.toInstructions(this)
|
||||
|
||||
// Add the compiled list of instructions to the method.
|
||||
addInstructions(
|
||||
index,
|
||||
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size),
|
||||
)
|
||||
|
||||
implementation!!.apply {
|
||||
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||
// If the compiled instruction is not an offset instruction, skip it.
|
||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||
|
||||
/**
|
||||
* Create a new label for the instruction
|
||||
* and replace it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||
*/
|
||||
fun Instruction.makeNewLabel() {
|
||||
fun replaceOffset(
|
||||
i: BuilderOffsetInstruction,
|
||||
label: Label,
|
||||
): BuilderOffsetInstruction {
|
||||
return when (i) {
|
||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||
is BuilderInstruction22t ->
|
||||
BuilderInstruction22t(
|
||||
i.opcode,
|
||||
i.registerA,
|
||||
i.registerB,
|
||||
label,
|
||||
)
|
||||
|
||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||
else -> throw IllegalStateException(
|
||||
"A non-offset instruction was given, this should never happen!",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final label.
|
||||
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
|
||||
|
||||
// Create the final instruction with the new label.
|
||||
val newInstruction =
|
||||
replaceOffset(
|
||||
compiledInstruction,
|
||||
label,
|
||||
)
|
||||
|
||||
// Replace the instruction pointing to the dummy label
|
||||
// with the new instruction pointing to the real instruction.
|
||||
replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||
}
|
||||
|
||||
// If the compiled instruction targets its own instruction,
|
||||
// which means it points to some of its own, simply an offset has to be applied.
|
||||
val labelIndex = compiledInstruction.target.location.index
|
||||
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||
this.instructions[index + labelIndex].makeNewLabel()
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Since the compiled instruction points to a dummy instruction,
|
||||
// we can find the real instruction which it was created for by calculation.
|
||||
|
||||
// Get the index of the instruction in the externalLabels list
|
||||
// which the dummy instruction was created for.
|
||||
// This works because we created the dummy instructions in the same order as the externalLabels list.
|
||||
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||
instruction.makeNewLabel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an instruction at the given index.
|
||||
*
|
||||
* @param index The index to remove the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) = implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(
|
||||
index: Int,
|
||||
count: Int,
|
||||
) = implementation!!.removeInstructions(index, count)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(count: Int) = implementation!!.removeInstructions(count)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(
|
||||
index: Int,
|
||||
instruction: BuilderInstruction,
|
||||
) = implementation!!.replaceInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param smaliInstruction The smali instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(
|
||||
index: Int,
|
||||
smaliInstruction: String,
|
||||
) = implementation!!.replaceInstruction(index, smaliInstruction.toInstructions(this).first())
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = implementation!!.replaceInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param smaliInstructions The smali instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
) = implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MethodImplementation.getInstruction(index: Int) = instructions.elementAt(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun Method.getInstructionOrNull(index: Int): Instruction? = implementation?.getInstruction(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun Method.getInstruction(index: Int): Instruction = getInstructionOrNull(index)!!
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun <T> Method.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun <T> Method.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun MutableMethod.getInstructionOrNull(index: Int): BuilderInstruction? = implementation?.getInstruction(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = getInstructionOrNull(index)!!
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun <T> MutableMethod.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun <T> MutableMethod.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions or null if the method has no implementation.
|
||||
*/
|
||||
val Method.instructionsOrNull: Iterable<Instruction>? get() = implementation?.instructions
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions.
|
||||
*/
|
||||
val Method.instructions: Iterable<Instruction> get() = instructionsOrNull!!
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions or null if the method has no implementation.
|
||||
*/
|
||||
val MutableMethod.instructionsOrNull: MutableList<BuilderInstruction>? get() = implementation?.instructions
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions.
|
||||
*/
|
||||
val MutableMethod.instructions: MutableList<BuilderInstruction> get() = instructionsOrNull!!
|
||||
|
||||
/**
|
||||
* Create a label for the instruction at given index.
|
||||
*
|
||||
* @param index The index to create the label for the instruction at.
|
||||
* @return The label.
|
||||
*/
|
||||
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
|
||||
|
||||
/**
|
||||
* A class that represents a label for an instruction.
|
||||
* @param name The label name.
|
||||
* @param instruction The instruction that this label is for.
|
||||
*/
|
||||
data class ExternalLabel(internal val name: String, internal val instruction: Instruction)
|
||||
@@ -1,264 +0,0 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.PatchesResult
|
||||
import app.revanced.patcher.extensions.instructionsOrNull
|
||||
import app.revanced.patcher.extensions.string
|
||||
import app.revanced.patcher.util.ClassMerger.merge
|
||||
import app.revanced.patcher.util.MethodNavigator
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableClassDef
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableClassDef.Companion.toMutable
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import lanchon.multidexlib2.RawDexIO
|
||||
import java.io.*
|
||||
import java.util.logging.Logger
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
/**
|
||||
* A context for patches containing the current state of the bytecode.
|
||||
*
|
||||
* @param apkFile The apk [File] to patch.
|
||||
* @param patchedFilesPath The path to the temporary apk files directory.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class BytecodePatchContext internal constructor(
|
||||
internal val apkFile: File,
|
||||
internal val patchedFilesPath: File,
|
||||
) : PatchContext<Set<PatchesResult.PatchedDexFile>> {
|
||||
private val logger = Logger.getLogger(this::class.jvmName)
|
||||
|
||||
inner class ClassDefs private constructor(
|
||||
dexFile: DexFile,
|
||||
private val classDefs: MutableSet<ClassDef> = dexFile.classes.toMutableSet()
|
||||
) :
|
||||
MutableSet<ClassDef> by classDefs {
|
||||
private val byType = mutableMapOf<String, ClassDef>()
|
||||
|
||||
operator fun get(name: String): ClassDef? = byType[name]
|
||||
|
||||
// Better performance according to
|
||||
// https://github.com/LisoUseInAIKyrios/revanced-patcher/commit/9b6d95d4f414a35ed68da37b0ecd8549df1ef63a
|
||||
private val _methodsByStrings =
|
||||
LinkedHashMap<String, MutableSet<Method>>(2 * size, 0.5f)
|
||||
|
||||
val methodsByString: Map<String, Set<Method>> = _methodsByStrings
|
||||
|
||||
// Can have a use-case in the future:
|
||||
// private val _methodsWithString = methodsByString.values.flatten().toMutableSet()
|
||||
// val methodsWithString: Set<Method> = _methodsWithString
|
||||
|
||||
constructor() : this(
|
||||
MultiDexIO.readDexFile(
|
||||
true,
|
||||
apkFile,
|
||||
BasicDexFileNamer(),
|
||||
null,
|
||||
null
|
||||
)
|
||||
)
|
||||
|
||||
internal val opcodes = dexFile.opcodes
|
||||
|
||||
override fun add(element: ClassDef): Boolean {
|
||||
val added = classDefs.add(element)
|
||||
if (added) addCache(element)
|
||||
|
||||
return added
|
||||
}
|
||||
|
||||
override fun addAll(elements: Collection<ClassDef>): Boolean {
|
||||
var anyAdded = false
|
||||
elements.forEach { element ->
|
||||
val added = classDefs.add(element)
|
||||
if (added) {
|
||||
addCache(element)
|
||||
anyAdded = true
|
||||
}
|
||||
}
|
||||
|
||||
return anyAdded
|
||||
}
|
||||
|
||||
// TODO: There is one default method "removeIf" in MutableSet, which we cannot override in the common module.
|
||||
// The method must be overloaded with a NotImplementedException to avoid cache desynchronization.
|
||||
|
||||
override fun clear() {
|
||||
classDefs.clear()
|
||||
byType.clear()
|
||||
_methodsByStrings.clear()
|
||||
}
|
||||
|
||||
override fun remove(element: ClassDef): Boolean {
|
||||
val removed = classDefs.remove(element)
|
||||
if (removed) removeCache(element)
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
override fun removeAll(elements: Collection<ClassDef>): Boolean {
|
||||
var anyRemoved = false
|
||||
elements.forEach { element ->
|
||||
val removed = classDefs.remove(element)
|
||||
if (removed) {
|
||||
removeCache(element)
|
||||
anyRemoved = true
|
||||
}
|
||||
}
|
||||
|
||||
return anyRemoved
|
||||
}
|
||||
|
||||
override fun retainAll(elements: Collection<ClassDef>) =
|
||||
removeAll(classDefs.asSequence().filter { it !in elements })
|
||||
|
||||
private fun addCache(classDef: ClassDef) {
|
||||
byType[classDef.type] = classDef
|
||||
|
||||
classDef.forEachString { method, string ->
|
||||
_methodsByStrings.getOrPut(string) {
|
||||
// Maybe adjusting load factor/ initial size can improve performance.
|
||||
mutableSetOf()
|
||||
} += method
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeCache(classDef: ClassDef) {
|
||||
byType -= classDef.type
|
||||
|
||||
classDef.forEachString { method, string ->
|
||||
if (_methodsByStrings[string]?.also { it -= method }?.isEmpty() == true)
|
||||
_methodsByStrings -= string
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun ClassDef.forEachString(action: (Method, String) -> Unit) {
|
||||
methods.asSequence().forEach { method ->
|
||||
method.instructionsOrNull?.asSequence()
|
||||
?.mapNotNull { it.string }
|
||||
?.forEach { string -> action(method, string) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mutable version of the given [classDef], replacing it in the set if necessary.
|
||||
*
|
||||
* @param classDef The [ClassDef] to get or replace.
|
||||
* @return The mutable version of the [classDef].
|
||||
* @see MutableClassDef
|
||||
* @see toMutable
|
||||
*/
|
||||
fun getOrReplaceMutable(classDef: ClassDef): MutableClassDef {
|
||||
if (classDef !is MutableClassDef) {
|
||||
val mutableClassDef = classDef.toMutable()
|
||||
this -= classDef
|
||||
this += mutableClassDef
|
||||
|
||||
return mutableClassDef
|
||||
}
|
||||
|
||||
return classDef
|
||||
}
|
||||
|
||||
internal fun initializeCache() = classDefs.forEach(::addCache)
|
||||
|
||||
internal fun clearCache() {
|
||||
byType.clear()
|
||||
_methodsByStrings.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of classes.
|
||||
*/
|
||||
val classDefs = ClassDefs()
|
||||
|
||||
/**
|
||||
* Extend this [BytecodePatchContext] with [extensionInputStream].
|
||||
*
|
||||
* @param extensionInputStream The input stream for an extension dex file.
|
||||
*/
|
||||
internal fun extendWith(extensionInputStream: InputStream) {
|
||||
RawDexIO.readRawDexFile(
|
||||
extensionInputStream, 0, null
|
||||
).classes.forEach { classDef ->
|
||||
val existingClass = classDefs[classDef.type] ?: run {
|
||||
logger.fine { "Adding class \"$classDef\"" }
|
||||
|
||||
classDefs += classDef
|
||||
|
||||
return@forEach
|
||||
}
|
||||
|
||||
logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
|
||||
|
||||
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
|
||||
// If the class was merged, replace the original class with the merged class.
|
||||
if (mergedClass === existingClass) {
|
||||
return@let
|
||||
}
|
||||
|
||||
classDefs -= existingClass
|
||||
classDefs += mergedClass
|
||||
}
|
||||
}
|
||||
|
||||
extensionInputStream.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate a method.
|
||||
*
|
||||
* @param method The method to navigate.
|
||||
*
|
||||
* @return A [MethodNavigator] for the method.
|
||||
*/
|
||||
fun navigate(method: MethodReference) = MethodNavigator(method)
|
||||
|
||||
/**
|
||||
* Compile bytecode from the [BytecodePatchContext].
|
||||
*
|
||||
* @return The compiled bytecode.
|
||||
*/
|
||||
override fun get(): Set<PatchesResult.PatchedDexFile> {
|
||||
logger.info("Compiling patched dex files")
|
||||
|
||||
classDefs.clearCache()
|
||||
System.gc()
|
||||
|
||||
val patchedDexFileResults =
|
||||
patchedFilesPath.resolve("dex").also {
|
||||
it.deleteRecursively() // Make sure the directory is empty.
|
||||
it.mkdirs()
|
||||
}.apply {
|
||||
MultiDexIO.writeDexFile(
|
||||
true,
|
||||
-1,
|
||||
this,
|
||||
BasicDexFileNamer(),
|
||||
object : DexFile {
|
||||
override fun getClasses() = classDefs.let {
|
||||
// More performant according to
|
||||
// https://github.com/LisoUseInAIKyrios/revanced-patcher/
|
||||
// commit/8c26ad08457fb1565ea5794b7930da42a1c81cf1
|
||||
// #diff-be698366d9868784ecf7da3fd4ac9d2b335b0bb637f9f618fbe067dbd6830b8fR197
|
||||
// TODO: Benchmark, if actually faster.
|
||||
HashSet<ClassDef>(it.size * 3 / 2).apply { addAll(it) }
|
||||
}
|
||||
|
||||
override fun getOpcodes() = classDefs.opcodes
|
||||
},
|
||||
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
|
||||
) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
|
||||
}.listFiles { it.isFile }!!.map {
|
||||
PatchesResult.PatchedDexFile(it.name, it.inputStream())
|
||||
}.toSet()
|
||||
|
||||
return patchedDexFileResults
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.lang.reflect.Member
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.function.Supplier
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
typealias PackageName = String
|
||||
typealias VersionName = String
|
||||
typealias Package = Pair<PackageName, Set<VersionName>?>
|
||||
|
||||
enum class PatchType(internal val prefix: String) {
|
||||
BYTECODE("Bytecode"),
|
||||
RAW_RESOURCE("RawResource"),
|
||||
RESOURCE("Resource")
|
||||
}
|
||||
|
||||
internal val Patch.patchesResources: Boolean get() = type == PatchType.RESOURCE || dependencies.any { it.patchesResources }
|
||||
|
||||
open class Patch internal constructor(
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val use: Boolean,
|
||||
val dependencies: Set<Patch>,
|
||||
val compatiblePackages: Set<Package>?,
|
||||
options: Set<Option<*>>,
|
||||
internal val apply: context(BytecodePatchContext, ResourcePatchContext) () -> Unit,
|
||||
// Must be nullable, so that Patcher.invoke can check,
|
||||
// if a patch has an "afterDependents" in order to not emit it twice.
|
||||
internal var afterDependents: (context(BytecodePatchContext, ResourcePatchContext) () -> Unit)?,
|
||||
internal val type: PatchType,
|
||||
) {
|
||||
val options = Options(options)
|
||||
|
||||
override fun toString() = name ?: "${type.prefix}Patch@${System.identityHashCode(this)}"
|
||||
}
|
||||
|
||||
sealed class PatchBuilder<C : PatchContext<*>>(
|
||||
private val type: PatchType,
|
||||
private val getPatchContext: context(BytecodePatchContext, ResourcePatchContext) () -> C
|
||||
) {
|
||||
private var compatiblePackages: MutableSet<Package>? = null
|
||||
private val dependencies = mutableSetOf<Patch>()
|
||||
private val options = mutableSetOf<Option<*>>()
|
||||
|
||||
internal var apply: context(BytecodePatchContext, ResourcePatchContext) () -> Unit = { }
|
||||
internal var afterDependents: (context(BytecodePatchContext, ResourcePatchContext) () -> Unit)? = null
|
||||
|
||||
context(_: BytecodePatchContext, _: ResourcePatchContext)
|
||||
private val patchContext get() = getPatchContext()
|
||||
|
||||
fun apply(block: C.() -> Unit) {
|
||||
apply = { block(patchContext) }
|
||||
}
|
||||
|
||||
fun afterDependents(block: C.() -> Unit) {
|
||||
afterDependents = { block(patchContext) }
|
||||
}
|
||||
|
||||
operator fun <T> Option<T>.invoke() = apply {
|
||||
options += this
|
||||
}
|
||||
|
||||
operator fun String.invoke(vararg versions: VersionName) = invoke(versions.toSet())
|
||||
|
||||
private operator fun String.invoke(versions: Set<VersionName>? = null): Package = this to versions
|
||||
|
||||
fun compatibleWith(vararg packages: Package) {
|
||||
if (compatiblePackages == null) {
|
||||
compatiblePackages = mutableSetOf()
|
||||
}
|
||||
|
||||
compatiblePackages!! += packages
|
||||
}
|
||||
|
||||
fun compatibleWith(vararg packages: String) = compatibleWith(*packages.map { it() }.toTypedArray())
|
||||
|
||||
fun dependsOn(vararg patches: Patch) {
|
||||
dependencies += patches
|
||||
}
|
||||
|
||||
|
||||
fun build(name: String?, description: String?, use: Boolean) = Patch(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
dependencies,
|
||||
compatiblePackages,
|
||||
options,
|
||||
apply,
|
||||
afterDependents,
|
||||
type,
|
||||
)
|
||||
}
|
||||
|
||||
class BytecodePatchBuilder private constructor(
|
||||
private var extensionInputStream: InputStream? = null
|
||||
) : PatchBuilder<BytecodePatchContext>(
|
||||
PatchType.BYTECODE,
|
||||
{
|
||||
// Extend the context with the extension, before returning it to the patch before applying it.
|
||||
contextOf<BytecodePatchContext>().apply {
|
||||
if (extensionInputStream != null) extendWith(extensionInputStream)
|
||||
}
|
||||
}
|
||||
) {
|
||||
internal constructor() : this(null)
|
||||
|
||||
fun extendWith(extension: String) {
|
||||
// Should be the classloader which loaded the patch class.
|
||||
val classLoader = Class.forName(Thread.currentThread().stackTrace[2].className).classLoader!!
|
||||
|
||||
extensionInputStream = classLoader.getResourceAsStream(extension)
|
||||
?: throw PatchException("Extension \"$extension\" not found")
|
||||
}
|
||||
}
|
||||
|
||||
open class ResourcePatchBuilder internal constructor(type: PatchType) : PatchBuilder<ResourcePatchContext>(
|
||||
type,
|
||||
{ contextOf<ResourcePatchContext>() }
|
||||
) {
|
||||
internal constructor() : this(PatchType.RESOURCE)
|
||||
}
|
||||
|
||||
class RawResourcePatchBuilder internal constructor() : ResourcePatchBuilder()
|
||||
|
||||
fun bytecodePatch(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: BytecodePatchBuilder.() -> Unit
|
||||
) = BytecodePatchBuilder().apply(block).build(name, description, use)
|
||||
|
||||
fun resourcePatch(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: ResourcePatchBuilder.() -> Unit
|
||||
) = ResourcePatchBuilder().apply(block).build(name, description, use)
|
||||
|
||||
fun rawResourcePatch(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: RawResourcePatchBuilder.() -> Unit
|
||||
) = RawResourcePatchBuilder().apply(block).build(name, description, use)
|
||||
|
||||
private fun <B : PatchBuilder<*>> creatingPatch(
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: B.() -> Unit,
|
||||
patchSupplier: (String?, String?, Boolean, B.() -> Unit) -> Patch
|
||||
) = ReadOnlyProperty<Any?, Patch> { _, property -> patchSupplier(property.name, description, use, block) }
|
||||
|
||||
fun creatingBytecodePatch(
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: BytecodePatchBuilder.() -> Unit,
|
||||
) = creatingPatch(description, use, block) { name, description, use, block ->
|
||||
bytecodePatch(name, description, use, block)
|
||||
}
|
||||
|
||||
fun creatingResourcePatch(
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: ResourcePatchBuilder.() -> Unit,
|
||||
) = creatingPatch(description, use, block) { name, description, use, block ->
|
||||
resourcePatch(name, description, use, block)
|
||||
}
|
||||
|
||||
fun creatingRawResourcePatch(
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: RawResourcePatchBuilder.() -> Unit,
|
||||
) = creatingPatch(description, use, block) { name, description, use, block ->
|
||||
rawResourcePatch(name, description, use, block)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A common interface for contexts such as [ResourcePatchContext] and [BytecodePatchContext].
|
||||
*/
|
||||
|
||||
sealed interface PatchContext<T> : Supplier<T>
|
||||
|
||||
/**
|
||||
* An exception thrown when patching.
|
||||
*
|
||||
* @param errorMessage The exception message.
|
||||
* @param cause The corresponding [Throwable].
|
||||
*/
|
||||
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
|
||||
constructor(errorMessage: String) : this(errorMessage, null)
|
||||
constructor(cause: Throwable) : this(cause.message, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* A result of applying a [Patch].
|
||||
*
|
||||
* @param patch The [Patch] that ran.
|
||||
* @param exception The [PatchException] thrown, if any.
|
||||
*/
|
||||
class PatchResult internal constructor(val patch: Patch, val exception: PatchException? = null)
|
||||
|
||||
/**
|
||||
* Creates a [PatchResult] for this [Patch].
|
||||
*
|
||||
* @param exception The [PatchException] thrown, if any.
|
||||
* @return The created [PatchResult].
|
||||
*/
|
||||
internal fun Patch.patchResult(exception: Exception? = null) = PatchResult(this, exception?.toPatchException())
|
||||
|
||||
/**
|
||||
* Creates a [PatchResult] for this [Patch] with the given error message.
|
||||
*
|
||||
* @param errorMessage The error message.
|
||||
* @return The created [PatchResult].
|
||||
*/
|
||||
internal fun Patch.patchResult(errorMessage: String) = PatchResult(this, PatchException(errorMessage))
|
||||
private fun Exception.toPatchException() = this as? PatchException ?: PatchException(this)
|
||||
|
||||
/**
|
||||
* A collection of patches loaded from patches files.
|
||||
*
|
||||
* @property patchesByFile The patches mapped by their patches file.
|
||||
*/
|
||||
class Patches internal constructor(val patchesByFile: Map<File, Set<Patch>>) : Set<Patch>
|
||||
by patchesByFile.values.flatten().toSet()
|
||||
|
||||
// Must be internal and a separate function for testing.
|
||||
@Suppress("MISSING_DEPENDENCY_IN_INFERRED_TYPE_ANNOTATION_WARNING")
|
||||
internal fun getPatches(classNames: List<String>, classLoader: ClassLoader): Set<Patch> {
|
||||
fun Member.isUsable() =
|
||||
Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && (this !is Method || parameterCount == 0)
|
||||
|
||||
fun Class<*>.getPatchFields() = fields
|
||||
.filter { it.type.isPatch && it.isUsable() }
|
||||
.map { it.get(null) as Patch }
|
||||
|
||||
fun Class<*>.getPatchMethods() = methods
|
||||
.filter { it.returnType.isPatch && it.parameterCount == 0 && it.isUsable() }
|
||||
.map { it.invoke(null) as Patch }
|
||||
|
||||
return classNames
|
||||
.map { classLoader.loadClass(it) }
|
||||
.flatMap { it.getPatchMethods() + it.getPatchFields() }
|
||||
.filter { it.name != null }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
internal fun loadPatches(
|
||||
vararg patchesFiles: File,
|
||||
getBinaryClassNames: (patchesFile: File) -> List<String>,
|
||||
classLoader: ClassLoader,
|
||||
onFailedToLoad: (File, Throwable) -> Unit
|
||||
) = Patches(patchesFiles.map { file ->
|
||||
file to getBinaryClassNames(file)
|
||||
}.mapNotNull { (file, classNames) ->
|
||||
runCatching { file to getPatches(classNames, classLoader) }
|
||||
.onFailure { onFailedToLoad(file, it) }.getOrNull()
|
||||
}.toMap())
|
||||
|
||||
expect fun loadPatches(
|
||||
vararg patchesFiles: File,
|
||||
onFailedToLoad: (patchesFile: File, throwable: Throwable) -> Unit = { _, _ -> },
|
||||
): Patches
|
||||
|
||||
internal expect val Class<*>.isPatch: Boolean
|
||||
@@ -1,229 +0,0 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.PatchesResult
|
||||
import app.revanced.patcher.util.Document
|
||||
import brut.androlib.AaptInvoker
|
||||
import brut.androlib.ApkDecoder
|
||||
import brut.androlib.Config
|
||||
import brut.androlib.apk.ApkInfo
|
||||
import brut.androlib.apk.UsesFramework
|
||||
import brut.androlib.res.Framework
|
||||
import brut.androlib.res.ResourcesDecoder
|
||||
import brut.androlib.res.decoder.AndroidManifestPullStreamDecoder
|
||||
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||
import brut.androlib.res.xml.ResXmlUtils
|
||||
import brut.directory.ExtFile
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.io.resolve
|
||||
import java.nio.file.Files
|
||||
import java.util.logging.Logger
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
/**
|
||||
* A context for patches containing the current state of resources.
|
||||
*
|
||||
* @param apkFile The apk file to patch.
|
||||
* @param apkFilesPath The path to the temporary apk files directory.
|
||||
* @param patchedFilesPath The path to the temporary patched files directory.
|
||||
* @param aaptBinaryPath The path to a custom aapt binary.
|
||||
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
|
||||
*/
|
||||
class ResourcePatchContext internal constructor(
|
||||
private val apkFile: File,
|
||||
private val apkFilesPath: File,
|
||||
private val patchedFilesPath: File,
|
||||
aaptBinaryPath: File? = null,
|
||||
frameworkFileDirectory: String? = null,
|
||||
) : PatchContext<PatchesResult.PatchedResources?> {
|
||||
private val apkInfo = ApkInfo(ExtFile(apkFile))
|
||||
|
||||
private val logger = Logger.getLogger(ResourcePatchContext::class.jvmName)
|
||||
|
||||
private val resourceConfig = Config.getDefaultConfig().apply {
|
||||
aaptBinary = aaptBinaryPath
|
||||
frameworkDirectory = frameworkFileDirectory
|
||||
}
|
||||
|
||||
internal var decodingMode = ResourceDecodingMode.MANIFEST
|
||||
|
||||
/**
|
||||
* Read a document from an [InputStream].
|
||||
*/
|
||||
fun document(inputStream: InputStream) = Document(inputStream)
|
||||
|
||||
/**
|
||||
* Read and write documents in the [apkFile].
|
||||
*/
|
||||
fun document(path: String) = Document(get(path))
|
||||
|
||||
/**
|
||||
* Set of resources from [apkFile] to delete.
|
||||
*/
|
||||
private val deleteResources = mutableSetOf<String>()
|
||||
|
||||
internal fun decodeManifest(): Pair<PackageName, VersionName> {
|
||||
logger.info("Decoding manifest")
|
||||
|
||||
val resourcesDecoder = ResourcesDecoder(resourceConfig, apkInfo)
|
||||
|
||||
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||
// because it does not support decoding to an OutputStream.
|
||||
AndroidManifestPullStreamDecoder(
|
||||
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||
resourcesDecoder.newXmlSerializer(),
|
||||
).decode(
|
||||
apkInfo.apkFile.directory.getFileInput("AndroidManifest.xml"),
|
||||
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||
object : OutputStream() {
|
||||
override fun write(b: Int) { // Do nothing.
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||
// The call to AndroidManifestPullStreamDecoder.decode() above sets apkInfo.
|
||||
val packageName = resourcesDecoder.resTable.packageRenamed
|
||||
val packageVersion =
|
||||
apkInfo.versionInfo.versionName ?: apkInfo.versionInfo.versionCode
|
||||
|
||||
/*
|
||||
When the main resource package is not loaded, the ResTable is flagged as sparse.
|
||||
Because ResourcesDecoder.decodeResources loads the main package and is not called here,
|
||||
set sparseResources to false again to prevent the ResTable from being flagged as sparse falsely,
|
||||
in case ResourcesDecoder.decodeResources is not later used in the patching process
|
||||
to set sparseResources correctly.
|
||||
|
||||
See ARSCDecoder.readTableType for more info.
|
||||
*/
|
||||
apkInfo.sparseResources = false
|
||||
|
||||
return packageName to packageVersion
|
||||
}
|
||||
|
||||
internal fun decodeResources() {
|
||||
logger.info("Decoding resources")
|
||||
|
||||
val resourcesDecoder = ResourcesDecoder(resourceConfig, apkInfo).also {
|
||||
it.decodeResources(apkFilesPath)
|
||||
it.decodeManifest(apkFilesPath)
|
||||
}
|
||||
|
||||
// Record uncompressed files to preserve their state when recompiling.
|
||||
ApkDecoder(apkInfo, resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
||||
|
||||
// Get the ids of the used framework packages to include them for reference when recompiling.
|
||||
apkInfo.usesFramework = UsesFramework().apply {
|
||||
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile resources in [apkFilesPath].
|
||||
*
|
||||
* @return The [PatchesResult.PatchedResources].
|
||||
*/
|
||||
override fun get(): PatchesResult.PatchedResources {
|
||||
logger.info("Compiling patched resources")
|
||||
|
||||
val resourcesPath = patchedFilesPath.resolve("resources").also { it.mkdirs() }
|
||||
|
||||
val resourcesApkFile = if (decodingMode == ResourceDecodingMode.ALL) {
|
||||
val resourcesApkFile = resourcesPath.resolve("resources.apk").also { it.createNewFile() }
|
||||
|
||||
val manifestFile = apkFilesPath.resolve("AndroidManifest.xml").also {
|
||||
ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
|
||||
}
|
||||
val resPath = apkFilesPath.resolve("res")
|
||||
val frameworkApkFiles = with(Framework(resourceConfig)) {
|
||||
apkInfo.usesFramework.ids.map { id -> getFrameworkApk(id, null) }
|
||||
}.toTypedArray()
|
||||
|
||||
AaptInvoker(
|
||||
resourceConfig,
|
||||
apkInfo
|
||||
).invoke(resourcesApkFile, manifestFile, resPath, null, null, frameworkApkFiles)
|
||||
|
||||
resourcesApkFile
|
||||
} else null
|
||||
|
||||
|
||||
val otherFiles = apkFilesPath.listFiles()!!.filter {
|
||||
// Excluded because present in resources.other.
|
||||
// TODO: We are reusing apkFiles as a temporarily directory for extracting resources.
|
||||
// This is not ideal as it could conflict with files such as the ones that are filtered here.
|
||||
// The problem is that ResourcePatchContext#get returns a File relative to apkFiles,
|
||||
// and we need to extract files to that directory.
|
||||
// A solution would be to use apkFiles as the working directory for the patching process.
|
||||
// Once all patches have been executed, we can move the decoded resources to a new directory.
|
||||
// The filters wouldn't be needed anymore.
|
||||
// For now, we assume that the files we filter here are not needed for the patching process.
|
||||
it.name != "AndroidManifest.xml" &&
|
||||
it.name != "res" &&
|
||||
// Generated by Androlib.
|
||||
it.name != "build"
|
||||
}
|
||||
val otherResourceFiles = if (otherFiles.isNotEmpty()) {
|
||||
// Move the other resources files.
|
||||
resourcesPath.resolve("other").also { it.mkdirs() }.apply {
|
||||
otherFiles.forEach { file ->
|
||||
Files.move(file.toPath(), resolve(file.name).toPath())
|
||||
}
|
||||
}
|
||||
} else null
|
||||
|
||||
return PatchesResult.PatchedResources(
|
||||
resourcesApkFile,
|
||||
otherResourceFiles,
|
||||
apkInfo.doNotCompress?.toSet() ?: emptySet(),
|
||||
deleteResources,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file from [apkFilesPath].
|
||||
*
|
||||
* @param path The path of the file.
|
||||
* @param copy Whether to copy the file from [apkFile] if it does not exist yet in [apkFilesPath].
|
||||
*/
|
||||
operator fun get(
|
||||
path: String,
|
||||
copy: Boolean = true,
|
||||
) = apkFilesPath.resolve(path).apply {
|
||||
if (copy && !exists()) {
|
||||
with(ExtFile(apkFile).directory) {
|
||||
if (containsFile(path) || containsDir(path)) {
|
||||
copyToDir(apkFilesPath, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a file for deletion when the APK is rebuilt.
|
||||
*
|
||||
* @param name The name of the file to delete.
|
||||
*/
|
||||
fun delete(name: String) = deleteResources.add(name)
|
||||
|
||||
/**
|
||||
* How to handle resources decoding and compiling.
|
||||
*/
|
||||
internal enum class ResourceDecodingMode {
|
||||
/**
|
||||
* Decode and compile all resources.
|
||||
*/
|
||||
ALL,
|
||||
|
||||
/**
|
||||
* Do not decode or compile any resources.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Do not decode or compile any resources.
|
||||
*/
|
||||
MANIFEST,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package collections
|
||||
|
||||
internal expect fun <K, V> MutableMap<K, V>.kmpMerge(
|
||||
key: K,
|
||||
value: V,
|
||||
remappingFunction: (oldValue: V, newValue: V) -> V,
|
||||
)
|
||||
|
||||
internal fun <K, V> MutableMap<K, V>.merge(
|
||||
key: K,
|
||||
value: V,
|
||||
remappingFunction: (oldValue: V, newValue: V) -> V,
|
||||
) = kmpMerge(key, value, remappingFunction)
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableAnnotationElement.Companion.toMutable
|
||||
|
||||
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) :
|
||||
BaseAnnotationEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var type = annotationEncodedValue.type
|
||||
|
||||
private val _elements by lazy {
|
||||
annotationEncodedValue.elements.map { annotationElement -> annotationElement.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
fun setType(type: String) {
|
||||
this.type = type
|
||||
}
|
||||
|
||||
override fun getType() = this.type
|
||||
|
||||
override fun getElements() = _elements
|
||||
|
||||
companion object {
|
||||
fun AnnotationEncodedValue.toMutable() = MutableAnnotationEncodedValue(this)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseArrayEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
|
||||
|
||||
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
|
||||
private val _value by lazy {
|
||||
arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
|
||||
}
|
||||
|
||||
override fun getValue() = _value
|
||||
|
||||
companion object {
|
||||
fun ArrayEncodedValue.toMutable(): MutableArrayEncodedValue = MutableArrayEncodedValue(this)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
|
||||
|
||||
class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.android.tools.smali.dexlib2.mutable
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethodParameter.Companion.toMutable
|
||||
|
||||
class MutableMethod(method: Method) : BaseMethodReference(), Method {
|
||||
private var definingClass = method.definingClass
|
||||
private var name = method.name
|
||||
private var accessFlags = method.accessFlags
|
||||
private var returnType = method.returnType
|
||||
|
||||
// TODO: Create own mutable MethodImplementation (due to not being able to change members like register count).
|
||||
private var implementation = method.implementation?.let(::MutableMethodImplementation)
|
||||
private val _annotations by lazy { method.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
|
||||
private val _parameters by lazy { method.parameters.map { parameter -> parameter.toMutable() }.toMutableList() }
|
||||
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
|
||||
private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
|
||||
|
||||
fun setDefiningClass(definingClass: String) {
|
||||
this.definingClass = definingClass
|
||||
}
|
||||
|
||||
fun setName(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun setAccessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
fun setReturnType(returnType: String) {
|
||||
this.returnType = returnType
|
||||
}
|
||||
|
||||
fun setImplementation(implementation: MutableMethodImplementation?) {
|
||||
this.implementation = implementation
|
||||
}
|
||||
|
||||
override fun getDefiningClass() = definingClass
|
||||
|
||||
override fun getName() = name
|
||||
|
||||
override fun getParameterTypes() = _parameterTypes
|
||||
|
||||
override fun getReturnType() = returnType
|
||||
|
||||
override fun getAnnotations() = _annotations
|
||||
|
||||
override fun getAccessFlags() = accessFlags
|
||||
|
||||
override fun getHiddenApiRestrictions() = _hiddenApiRestrictions
|
||||
|
||||
override fun getParameters() = _parameters
|
||||
|
||||
override fun getImplementation() = implementation
|
||||
|
||||
companion object {
|
||||
fun Method.toMutable() = MutableMethod(this)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.android.tools.smali.dexlib2.mutable
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.BaseMethodParameter
|
||||
import com.android.tools.smali.dexlib2.iface.MethodParameter
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
|
||||
|
||||
class MutableMethodParameter(parameter: MethodParameter) : BaseMethodParameter(), MethodParameter {
|
||||
private var type = parameter.type
|
||||
private var name = parameter.name
|
||||
private var signature = parameter.signature
|
||||
private val _annotations by lazy {
|
||||
parameter.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
override fun getType() = type
|
||||
|
||||
override fun getName() = name
|
||||
|
||||
override fun getSignature() = signature
|
||||
|
||||
override fun getAnnotations() = _annotations
|
||||
|
||||
companion object {
|
||||
fun MethodParameter.toMutable() = MutableMethodParameter(this)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package java.io
|
||||
|
||||
internal expect fun File.kmpResolve(child: String): File
|
||||
|
||||
internal fun File.resolve(child: String) = kmpResolve(child)
|
||||
|
||||
internal expect fun File.kmpDeleteRecursively(): Boolean
|
||||
|
||||
internal fun File.deleteRecursively() = kmpDeleteRecursively()
|
||||
|
||||
internal expect fun File.kmpInputStream(): InputStream
|
||||
|
||||
internal fun File.inputStream() = kmpInputStream()
|
||||
|
||||
internal expect fun File.kmpBufferedWriter(charset: java.nio.charset.Charset): BufferedWriter
|
||||
|
||||
internal fun File.bufferedWriter(charset: java.nio.charset.Charset) = kmpBufferedWriter(charset)
|
||||
@@ -1,32 +0,0 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
actual val Class<*>.isPatch get() = Patch::class.java.isAssignableFrom(this)
|
||||
|
||||
/**
|
||||
* Loads patches from JAR files declared as public static fields
|
||||
* or returned by public static and non-parametrized methods.
|
||||
* Patches with no name are not loaded. If a patches file fails to load,
|
||||
* the [onFailedToLoad] callback is invoked with the file and the throwable
|
||||
* and the loading continues for the other files.
|
||||
*
|
||||
* @param patchesFiles The JAR files to load the patches from.
|
||||
* @param onFailedToLoad A callback invoked when a patches file fails to load.
|
||||
*
|
||||
* @return The loaded patches.
|
||||
*/
|
||||
actual fun loadPatches(
|
||||
vararg patchesFiles: File,
|
||||
onFailedToLoad: (patchesFile: File, throwable: Throwable) -> Unit,
|
||||
) = loadPatches(
|
||||
patchesFiles = patchesFiles,
|
||||
{ file ->
|
||||
JarFile(file).entries().toList().filter { it.name.endsWith(".class") }
|
||||
.map { it.name.substringBeforeLast('.').replace('/', '.') }
|
||||
},
|
||||
URLClassLoader(patchesFiles.map { it.toURI().toURL() }.toTypedArray()),
|
||||
onFailedToLoad = onFailedToLoad
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
package collections
|
||||
|
||||
internal actual fun <K, V> MutableMap<K, V>.kmpMerge(
|
||||
key: K,
|
||||
value: V,
|
||||
remappingFunction: (oldValue: V, newValue: V) -> V
|
||||
) = merge(key, value, remappingFunction)
|
||||
@@ -1,9 +0,0 @@
|
||||
package java.io
|
||||
|
||||
import java.nio.charset.Charset
|
||||
|
||||
|
||||
internal actual fun File.kmpResolve(child: String) = resolve(child)
|
||||
internal actual fun File.kmpDeleteRecursively() = deleteRecursively()
|
||||
internal actual fun File.kmpInputStream() = inputStream()
|
||||
internal actual fun File.kmpBufferedWriter(charset: Charset) = bufferedWriter(charset)
|
||||
@@ -1,27 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class FingerprintTest : PatcherTestBase() {
|
||||
@BeforeAll
|
||||
fun setup() = setupMock()
|
||||
|
||||
@Test
|
||||
fun `matches fingerprints correctly`() {
|
||||
with(bytecodePatchContext) {
|
||||
assertNotNull(
|
||||
fingerprint { returns("V") }.originalMethodOrNull,
|
||||
"Fingerprints should match correctly."
|
||||
)
|
||||
assertNull(
|
||||
fingerprint { returns("does not exist") }.originalMethodOrNull,
|
||||
"Fingerprints should match correctly."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.BytecodePatchContextMethodMatching.firstMethod
|
||||
import app.revanced.patcher.BytecodePatchContextMethodMatching.firstMethodDeclarativelyOrNull
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22t
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class MatchingTest : PatcherTestBase() {
|
||||
@BeforeAll
|
||||
fun setup() = setupMock()
|
||||
|
||||
@Test
|
||||
fun `finds via builder api`() {
|
||||
fun firstMethodComposite(fail: Boolean = false) = firstMethodComposite {
|
||||
name("method")
|
||||
definingClass("class")
|
||||
|
||||
if (fail) returnType("doesnt exist")
|
||||
|
||||
instructions(
|
||||
at(Opcode.CONST_STRING()),
|
||||
`is`<TwoRegisterInstruction>(),
|
||||
noneOf(registers()),
|
||||
string("test", String::contains),
|
||||
after(1..3, allOf(Opcode.INVOKE_VIRTUAL(), registers(1, 0))),
|
||||
allOf(),
|
||||
type("PrintStream;", String::endsWith)
|
||||
)
|
||||
}
|
||||
|
||||
with(bytecodePatchContext) {
|
||||
assertNotNull(firstMethodComposite().methodOrNull) { "Expected to find a method" }
|
||||
Assertions.assertNull(firstMethodComposite(fail = true).immutableMethodOrNull) { "Expected to not find a method" }
|
||||
Assertions.assertNotNull(
|
||||
firstMethodComposite().match(classDefs.first()).methodOrNull
|
||||
) { "Expected to find a method matching in a specific class" }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `finds via declarative api`() {
|
||||
bytecodePatch {
|
||||
apply {
|
||||
val method = firstMethodDeclarativelyOrNull {
|
||||
anyOf {
|
||||
predicate { name == "method" }
|
||||
add { false }
|
||||
}
|
||||
allOf {
|
||||
predicate { returnType == "V" }
|
||||
}
|
||||
predicate { definingClass == "class" }
|
||||
}
|
||||
assertNotNull(method) { "Expected to find a method" }
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `predicate api works correctly`() {
|
||||
bytecodePatch {
|
||||
apply {
|
||||
assertDoesNotThrow("Should find method") { firstMethod { name == "method" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `matcher finds indices correctly`() {
|
||||
val iterable = (1..10).toList()
|
||||
val matcher = indexedMatcher<Int>()
|
||||
|
||||
matcher.apply {
|
||||
+at<Int> { this > 5 }
|
||||
}
|
||||
assertFalse(
|
||||
matcher(iterable),
|
||||
"Should not match at any other index than first"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply { +at<Int> { this == 1 } }(iterable)
|
||||
assertEquals(
|
||||
listOf(0),
|
||||
matcher.indices,
|
||||
"Should match at first index."
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply { add { _, _, _ -> this > 0 } }(iterable)
|
||||
assertEquals(1, matcher.indices.size, "Should only match once.")
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply { add { _, _, _ -> this == 2 } }(iterable)
|
||||
assertEquals(
|
||||
listOf(1),
|
||||
matcher.indices,
|
||||
"Should find the index correctly."
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
+at<Int> { this == 1 }
|
||||
add { _, _, _ -> this == 2 }
|
||||
add { _, _, _ -> this == 4 }
|
||||
}(iterable)
|
||||
assertEquals(
|
||||
listOf(0, 1, 3),
|
||||
matcher.indices,
|
||||
"Should match 1, 2 and 4 at indices 0, 1 and 3."
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
+after<Int> { this == 1 }
|
||||
}(iterable)
|
||||
assertEquals(
|
||||
listOf(0),
|
||||
matcher.indices,
|
||||
"Should match index 0 after nothing"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
+after<Int>(2..Int.MAX_VALUE) { this == 1 }
|
||||
}
|
||||
assertFalse(
|
||||
matcher(iterable),
|
||||
"Should not match, because 1 is out of range"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
+after<Int>(1..1) { this == 2 }
|
||||
}
|
||||
assertFalse(
|
||||
matcher(iterable),
|
||||
"Should not match, because 2 is at index 1"
|
||||
)
|
||||
matcher.clear()
|
||||
|
||||
matcher.apply {
|
||||
+at<Int> { this == 1 }
|
||||
+after<Int>(2..5) { this == 4 }
|
||||
add { _, _, _ -> this == 8 }
|
||||
add { _, _, _ -> this == 9 }
|
||||
}(iterable)
|
||||
assertEquals(
|
||||
listOf(0, 3, 7, 8),
|
||||
matcher.indices,
|
||||
"Should match indices correctly."
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class PatcherTest : PatcherTestBase() {
|
||||
@BeforeAll
|
||||
fun setup() = setupMock()
|
||||
|
||||
@Test
|
||||
fun `applies patches in correct order`() {
|
||||
val applied = mutableListOf<String>()
|
||||
|
||||
infix fun Patch.resultsIn(equals: List<String>) = this to equals
|
||||
infix fun Pair<Patch, List<String>>.because(reason: String) {
|
||||
runCatching { setOf(first)() }
|
||||
|
||||
assertEquals(second, applied, reason)
|
||||
|
||||
applied.clear()
|
||||
}
|
||||
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
apply { applied += "1" }
|
||||
afterDependents { applied += "-2" }
|
||||
},
|
||||
bytecodePatch { apply { applied += "2" } },
|
||||
)
|
||||
apply { applied += "3" }
|
||||
afterDependents { applied += "-1" }
|
||||
} resultsIn listOf("1", "2", "3", "-1", "-2") because
|
||||
"Patches should apply in post-order and afterDependents in pre-order."
|
||||
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
apply { throw PatchException("1") }
|
||||
afterDependents { applied += "-2" }
|
||||
},
|
||||
)
|
||||
apply { applied += "2" }
|
||||
afterDependents { applied += "-1" }
|
||||
} resultsIn emptyList() because
|
||||
"Patches that depend on a patched that failed to apply should not be applied."
|
||||
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
apply { applied += "1" }
|
||||
afterDependents { applied += "-2" }
|
||||
},
|
||||
)
|
||||
apply { throw PatchException("2") }
|
||||
afterDependents { applied += "-1" }
|
||||
} resultsIn listOf("1", "-2") because
|
||||
"afterDependents of a patch should not be called if it failed to apply."
|
||||
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
apply { applied += "1" }
|
||||
afterDependents { applied += "-2" }
|
||||
},
|
||||
)
|
||||
apply { applied += "2" }
|
||||
afterDependents { throw PatchException("-1") }
|
||||
} resultsIn listOf("1", "2", "-2") because
|
||||
"afterDependents of a patch should be called " +
|
||||
"regardless of dependant patches failing."
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.extensions.toInstructions
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.ResourcePatchContext
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
abstract class PatcherTestBase {
|
||||
protected lateinit var bytecodePatchContext: BytecodePatchContext
|
||||
protected lateinit var resourcePatchContext: ResourcePatchContext
|
||||
|
||||
protected fun setupMock(
|
||||
method: ImmutableMethod = ImmutableMethod(
|
||||
"class",
|
||||
"method",
|
||||
emptyList(),
|
||||
"V",
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
ImmutableMethodImplementation(
|
||||
2,
|
||||
"""
|
||||
const-string v0, "Hello, World!"
|
||||
iput-object v0, p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
iget-object v0, p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
return-void
|
||||
const-string v0, "This is a test."
|
||||
return-object v0
|
||||
invoke-virtual { p0, v0 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
invoke-static { p0 }, Ljava/lang/System;->currentTimeMillis()J
|
||||
check-cast p0, Ljava/io/PrintStream;
|
||||
""".toInstructions(),
|
||||
null,
|
||||
null
|
||||
),
|
||||
),
|
||||
) {
|
||||
resourcePatchContext = mockk<ResourcePatchContext>(relaxed = true)
|
||||
bytecodePatchContext = mockk<BytecodePatchContext> bytecodePatchContext@{
|
||||
mockkStatic(MultiDexIO::readDexFile)
|
||||
every {
|
||||
MultiDexIO.readDexFile(
|
||||
any(),
|
||||
any(),
|
||||
any(),
|
||||
any(),
|
||||
any()
|
||||
)
|
||||
} returns mockk<DexFile> {
|
||||
every { classes } returns mutableSetOf(
|
||||
ImmutableClassDef(
|
||||
"class",
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
listOf(method),
|
||||
)
|
||||
)
|
||||
every { opcodes } returns Opcodes.getDefault()
|
||||
}
|
||||
|
||||
every { this@bytecodePatchContext.getProperty("apkFile") } returns mockk<File>()
|
||||
|
||||
every { this@bytecodePatchContext.classDefs } returns ClassDefs().apply {
|
||||
javaClass.getDeclaredMethod($$"initializeCache$patcher").apply {
|
||||
isAccessible = true
|
||||
}.invoke(this)
|
||||
}
|
||||
|
||||
every { get() } returns emptySet()
|
||||
|
||||
justRun { this@bytecodePatchContext["extendWith"](any<InputStream>()) }
|
||||
}
|
||||
}
|
||||
|
||||
protected operator fun Set<Patch>.invoke() {
|
||||
runCatching {
|
||||
apply(
|
||||
bytecodePatchContext,
|
||||
resourcePatchContext
|
||||
) { }
|
||||
}.fold(
|
||||
{ it.dexFiles },
|
||||
{ it.printStackTrace() }
|
||||
)
|
||||
}
|
||||
|
||||
protected operator fun Patch.invoke() = setOf(this)()
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal object PatchTest {
|
||||
@Test
|
||||
fun `can create patch with name`() {
|
||||
val patch = bytecodePatch(name = "Test") {}
|
||||
|
||||
assertEquals("Test", patch.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can create patch with compatible packages`() {
|
||||
val patch = bytecodePatch(name = "Test") {
|
||||
compatibleWith(
|
||||
"compatible.package"("1.0.0"),
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(1, patch.compatiblePackages!!.size)
|
||||
assertEquals("compatible.package", patch.compatiblePackages!!.first().first)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can create patch with dependencies`() {
|
||||
val patch = bytecodePatch(name = "Test") {
|
||||
dependsOn(resourcePatch {})
|
||||
}
|
||||
|
||||
assertEquals(1, patch.dependencies.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can create patch with options`() {
|
||||
val patch = bytecodePatch(name = "Test") {
|
||||
val print by stringOption("print")
|
||||
val custom = option<String>("custom")()
|
||||
|
||||
this.apply {
|
||||
println(print)
|
||||
println(custom.value)
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(2, patch.options.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loads patches correctly`() {
|
||||
val patchesClass = ::Public.javaField!!.declaringClass.name
|
||||
val classLoader = ::Public.javaClass.classLoader
|
||||
|
||||
val patches = getPatches(listOf(patchesClass), classLoader)
|
||||
|
||||
assertEquals(
|
||||
2,
|
||||
patches.size,
|
||||
"Expected 2 patches to be loaded, " +
|
||||
"because there's only two named patches declared as public static fields " +
|
||||
"or returned by public static and non-parametrized methods.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val publicUnnamedPatch = bytecodePatch {} // Not loaded, because it's unnamed.
|
||||
|
||||
val Public by creatingBytecodePatch {} // Loaded, because it's named.
|
||||
|
||||
private val privateUnnamedPatch = bytecodePatch {} // Not loaded, because it's private.
|
||||
|
||||
private val Private by creatingBytecodePatch {} // Not loaded, because it's private.
|
||||
|
||||
fun publicUnnamedPatchFunction() = publicUnnamedPatch // Not loaded, because it's unnamed.
|
||||
|
||||
fun publicNamedPatchFunction() = bytecodePatch("Public") { } // Loaded, because it's named.
|
||||
|
||||
fun parameterizedFunction(@Suppress("UNUSED_PARAMETER") param: Any) =
|
||||
publicNamedPatchFunction() // Not loaded, because it's parameterized.
|
||||
|
||||
private fun privateUnnamedPatchFunction() = privateUnnamedPatch // Not loaded, because it's private.
|
||||
|
||||
private fun privateNamedPatchFunction() = Private // Not loaded, because it's private.
|
||||
@@ -1,76 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.extensions.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21t
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class SmaliTest {
|
||||
val method = ImmutableMethod(
|
||||
"Ldummy;",
|
||||
"name",
|
||||
emptyList(), // parameters
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(1),
|
||||
).toMutable()
|
||||
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
method.instructions.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `own branches work`() {
|
||||
method.addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
:test
|
||||
const/4 v0, 0x1
|
||||
if-eqz v0, :test
|
||||
""",
|
||||
)
|
||||
|
||||
val targetLocationIndex = method.getInstruction<BuilderOffsetInstruction>(1).target.location.index
|
||||
|
||||
assertEquals(0, targetLocationIndex, "Label should point to index 0")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `external branches work`() {
|
||||
val instructionIndex = 3
|
||||
val labelIndex = 1
|
||||
|
||||
method.addInstructions(
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
const/4 v0, 0x0
|
||||
""",
|
||||
)
|
||||
|
||||
method.addInstructionsWithLabels(
|
||||
method.instructions.size,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
if-eqz v0, :test
|
||||
return-void
|
||||
""",
|
||||
ExternalLabel("test", method.getInstruction(1)),
|
||||
)
|
||||
|
||||
val instruction = method.getInstruction<BuilderInstruction21t>(instructionIndex)
|
||||
|
||||
assertTrue(instruction.target.isPlaced, "Label should be placed")
|
||||
assertEquals(labelIndex, instruction.target.location.index)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1 @@
|
||||
rootProject.name = "revanced-patcher"
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven {
|
||||
name = "githubPackages"
|
||||
// A repository must be specified for some reason. "registry" is a dummy.
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include(":patcher")
|
||||
|
||||
599
src/main/kotlin/app/revanced/patcher/Fingerprint.kt
Normal file
599
src/main/kotlin/app/revanced/patcher/Fingerprint.kt
Normal file
@@ -0,0 +1,599 @@
|
||||
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
/**
|
||||
* A fingerprint for a method. A fingerprint is a partial description of a method.
|
||||
* It is used to uniquely match a method by its characteristics.
|
||||
*
|
||||
* An example fingerprint for a public method that takes a single string parameter and returns void:
|
||||
* ```
|
||||
* fingerprint {
|
||||
* accessFlags(AccessFlags.PUBLIC)
|
||||
* returns("V")
|
||||
* parameters("Ljava/lang/String;")
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param accessFlags The exact access flags using values of [AccessFlags].
|
||||
* @param returnType The return type. Compared using [String.startsWith].
|
||||
* @param parameters The parameters. Partial matches allowed and follow the same rules as [returnType].
|
||||
* @param opcodes A pattern of instruction opcodes. `null` can be used as a wildcard.
|
||||
* @param strings A list of the strings. Compared using [String.contains].
|
||||
* @param custom A custom condition for this fingerprint.
|
||||
* @param fuzzyPatternScanThreshold The threshold for fuzzy scanning the [opcodes] pattern.
|
||||
*/
|
||||
class Fingerprint internal constructor(
|
||||
internal val accessFlags: Int?,
|
||||
internal val returnType: String?,
|
||||
internal val parameters: List<String>?,
|
||||
internal val opcodes: List<Opcode?>?,
|
||||
internal val strings: List<String>?,
|
||||
internal val custom: ((method: Method, classDef: ClassDef) -> Boolean)?,
|
||||
private val fuzzyPatternScanThreshold: Int,
|
||||
) {
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
// Backing field needed for lazy initialization.
|
||||
private var _matchOrNull: Match? = null
|
||||
|
||||
/**
|
||||
* The match for this [Fingerprint]. Null if unmatched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
private val matchOrNull: Match?
|
||||
get() = matchOrNull()
|
||||
|
||||
/**
|
||||
* Match using [BytecodePatchContext.lookupMaps].
|
||||
*
|
||||
* Generally faster than the other [matchOrNull] overloads when there are many methods to check for a match.
|
||||
*
|
||||
* Fingerprints can be optimized for performance:
|
||||
* - Slowest: Specify [custom] or [opcodes] and nothing else.
|
||||
* - Fast: Specify [accessFlags], [returnType].
|
||||
* - Faster: Specify [accessFlags], [returnType] and [parameters].
|
||||
* - Fastest: Specify [strings], with at least one string being an exact (non-partial) match.
|
||||
*
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
internal fun matchOrNull(): Match? {
|
||||
if (_matchOrNull != null) return _matchOrNull
|
||||
|
||||
var match = strings?.mapNotNull {
|
||||
lookupMaps.methodsByStrings[it]
|
||||
}?.minByOrNull { it.size }?.let { methodClasses ->
|
||||
methodClasses.forEach { (classDef, method) ->
|
||||
val match = matchOrNull(classDef, method)
|
||||
if (match != null) return@let match
|
||||
}
|
||||
|
||||
null
|
||||
}
|
||||
if (match != null) return match
|
||||
|
||||
classes.forEach { classDef ->
|
||||
match = matchOrNull(classDef)
|
||||
if (match != null) return match
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Match using a [ClassDef].
|
||||
*
|
||||
* @param classDef The class to match against.
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
fun matchOrNull(
|
||||
classDef: ClassDef,
|
||||
): Match? {
|
||||
if (_matchOrNull != null) return _matchOrNull
|
||||
|
||||
for (method in classDef.methods) {
|
||||
val match = matchOrNull(method, classDef)
|
||||
if (match != null) return match
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Match using a [Method].
|
||||
* The class is retrieved from the method.
|
||||
*
|
||||
* @param method The method to match against.
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
fun matchOrNull(
|
||||
method: Method,
|
||||
) = matchOrNull(method, classBy { method.definingClass == it.type }!!.immutableClass)
|
||||
|
||||
/**
|
||||
* Match using a [Method].
|
||||
*
|
||||
* @param method The method to match against.
|
||||
* @param classDef The class the method is a member of.
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
fun matchOrNull(
|
||||
method: Method,
|
||||
classDef: ClassDef,
|
||||
): Match? {
|
||||
if (_matchOrNull != null) return _matchOrNull
|
||||
|
||||
if (returnType != null && !method.returnType.startsWith(returnType)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (accessFlags != null && accessFlags != method.accessFlags) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>,
|
||||
parameters2: Iterable<CharSequence>,
|
||||
): Boolean {
|
||||
if (parameters1.count() != parameters2.count()) return false
|
||||
val iterator1 = parameters1.iterator()
|
||||
parameters2.forEach {
|
||||
if (!it.startsWith(iterator1.next())) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: parseParameters()
|
||||
if (parameters != null && !parametersEqual(parameters, method.parameterTypes)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (custom != null && !custom.invoke(method, classDef)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val stringMatches: List<Match.StringMatch>? =
|
||||
if (strings != null) {
|
||||
buildList {
|
||||
val instructions = method.instructionsOrNull ?: return null
|
||||
|
||||
val stringsList = strings.toMutableList()
|
||||
|
||||
instructions.forEachIndexed { instructionIndex, instruction ->
|
||||
if (
|
||||
instruction.opcode != Opcode.CONST_STRING &&
|
||||
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||
) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst(string::contains)
|
||||
if (index == -1) return@forEachIndexed
|
||||
|
||||
add(Match.StringMatch(string, instructionIndex))
|
||||
stringsList.removeAt(index)
|
||||
}
|
||||
|
||||
if (stringsList.isNotEmpty()) return null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val patternMatch = if (opcodes != null) {
|
||||
val instructions = method.instructionsOrNull ?: return null
|
||||
|
||||
fun patternScan(): Match.PatternMatch? {
|
||||
val fingerprintFuzzyPatternScanThreshold = fuzzyPatternScanThreshold
|
||||
|
||||
val instructionLength = instructions.count()
|
||||
val patternLength = opcodes.size
|
||||
|
||||
for (index in 0 until instructionLength) {
|
||||
var patternIndex = 0
|
||||
var threshold = fingerprintFuzzyPatternScanThreshold
|
||||
|
||||
while (index + patternIndex < instructionLength) {
|
||||
val originalOpcode = instructions.elementAt(index + patternIndex).opcode
|
||||
val patternOpcode = opcodes.elementAt(patternIndex)
|
||||
|
||||
if (patternOpcode != null && patternOpcode.ordinal != originalOpcode.ordinal) {
|
||||
// Reaching maximum threshold (0) means,
|
||||
// the pattern does not match to the current instructions.
|
||||
if (threshold-- == 0) break
|
||||
}
|
||||
|
||||
if (patternIndex < patternLength - 1) {
|
||||
// If the entire pattern has not been scanned yet, continue the scan.
|
||||
patternIndex++
|
||||
continue
|
||||
}
|
||||
|
||||
// The entire pattern has been scanned.
|
||||
return Match.PatternMatch(
|
||||
index,
|
||||
index + patternIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
patternScan() ?: return null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
_matchOrNull = Match(
|
||||
method,
|
||||
patternMatch,
|
||||
stringMatches,
|
||||
classDef,
|
||||
)
|
||||
|
||||
return _matchOrNull
|
||||
}
|
||||
|
||||
private val exception get() = PatchException("Failed to match the fingerprint: $this")
|
||||
|
||||
/**
|
||||
* The match for this [Fingerprint].
|
||||
*
|
||||
* @throws PatchException If the [Fingerprint] has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
private val match
|
||||
get() = matchOrNull ?: throw exception
|
||||
|
||||
/**
|
||||
* Match using a [ClassDef].
|
||||
*
|
||||
* @param classDef The class to match against.
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
fun match(
|
||||
classDef: ClassDef,
|
||||
) = matchOrNull(classDef) ?: throw exception
|
||||
|
||||
/**
|
||||
* Match using a [Method].
|
||||
* The class is retrieved from the method.
|
||||
*
|
||||
* @param method The method to match against.
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
fun match(
|
||||
method: Method,
|
||||
) = matchOrNull(method) ?: throw exception
|
||||
|
||||
/**
|
||||
* Match using a [Method].
|
||||
*
|
||||
* @param method The method to match against.
|
||||
* @param classDef The class the method is a member of.
|
||||
* @return The [Match] if a match was found or if the fingerprint is already matched to a method, null otherwise.
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
fun match(
|
||||
method: Method,
|
||||
classDef: ClassDef,
|
||||
) = matchOrNull(method, classDef) ?: throw exception
|
||||
|
||||
/**
|
||||
* The class the matching method is a member of.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val originalClassDefOrNull
|
||||
get() = matchOrNull?.originalClassDef
|
||||
|
||||
/**
|
||||
* The matching method.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val originalMethodOrNull
|
||||
get() = matchOrNull?.originalMethod
|
||||
|
||||
/**
|
||||
* The mutable version of [originalClassDefOrNull].
|
||||
*
|
||||
* Accessing this property allocates a [ClassProxy].
|
||||
* Use [originalClassDefOrNull] if mutable access is not required.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val classDefOrNull
|
||||
get() = matchOrNull?.classDef
|
||||
|
||||
/**
|
||||
* The mutable version of [originalMethodOrNull].
|
||||
*
|
||||
* Accessing this property allocates a [ClassProxy].
|
||||
* Use [originalMethodOrNull] if mutable access is not required.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val methodOrNull
|
||||
get() = matchOrNull?.method
|
||||
|
||||
/**
|
||||
* The match for the opcode pattern.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val patternMatchOrNull
|
||||
get() = matchOrNull?.patternMatch
|
||||
|
||||
/**
|
||||
* The matches for the strings.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val stringMatchesOrNull
|
||||
get() = matchOrNull?.stringMatches
|
||||
|
||||
/**
|
||||
* The class the matching method is a member of.
|
||||
*
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val originalClassDef
|
||||
get() = match.originalClassDef
|
||||
|
||||
/**
|
||||
* The matching method.
|
||||
*
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val originalMethod
|
||||
get() = match.originalMethod
|
||||
|
||||
/**
|
||||
* The mutable version of [originalClassDef].
|
||||
*
|
||||
* Accessing this property allocates a [ClassProxy].
|
||||
* Use [originalClassDef] if mutable access is not required.
|
||||
*
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val classDef
|
||||
get() = match.classDef
|
||||
|
||||
/**
|
||||
* The mutable version of [originalMethod].
|
||||
*
|
||||
* Accessing this property allocates a [ClassProxy].
|
||||
* Use [originalMethod] if mutable access is not required.
|
||||
*
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val method
|
||||
get() = match.method
|
||||
|
||||
/**
|
||||
* The match for the opcode pattern.
|
||||
*
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val patternMatch
|
||||
get() = match.patternMatch
|
||||
|
||||
/**
|
||||
* The matches for the strings.
|
||||
*
|
||||
* @throws PatchException If the fingerprint has not been matched.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
val stringMatches
|
||||
get() = match.stringMatches
|
||||
}
|
||||
|
||||
/**
|
||||
* A match of a [Fingerprint].
|
||||
*
|
||||
* @param originalClassDef The class the matching method is a member of.
|
||||
* @param originalMethod The matching method.
|
||||
* @param patternMatch The match for the opcode pattern.
|
||||
* @param stringMatches The matches for the strings.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
class Match internal constructor(
|
||||
val originalMethod: Method,
|
||||
val patternMatch: PatternMatch?,
|
||||
val stringMatches: List<StringMatch>?,
|
||||
val originalClassDef: ClassDef,
|
||||
) {
|
||||
/**
|
||||
* The mutable version of [originalClassDef].
|
||||
*
|
||||
* Accessing this property allocates a [ClassProxy].
|
||||
* Use [originalClassDef] if mutable access is not required.
|
||||
*/
|
||||
val classDef by lazy { proxy(originalClassDef).mutableClass }
|
||||
|
||||
/**
|
||||
* The mutable version of [originalMethod].
|
||||
*
|
||||
* Accessing this property allocates a [ClassProxy].
|
||||
* Use [originalMethod] if mutable access is not required.
|
||||
*/
|
||||
val method by lazy { classDef.methods.first { MethodUtil.methodSignaturesMatch(it, originalMethod) } }
|
||||
|
||||
/**
|
||||
* A match for an opcode pattern.
|
||||
* @param startIndex The index of the first opcode of the pattern in the method.
|
||||
* @param endIndex The index of the last opcode of the pattern in the method.
|
||||
*/
|
||||
class PatternMatch internal constructor(
|
||||
val startIndex: Int,
|
||||
val endIndex: Int,
|
||||
)
|
||||
|
||||
/**
|
||||
* A match for a string.
|
||||
*
|
||||
* @param string The string that matched.
|
||||
* @param index The index of the instruction in the method.
|
||||
*/
|
||||
class StringMatch internal constructor(val string: String, val index: Int)
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for [Fingerprint].
|
||||
*
|
||||
* @property accessFlags The exact access flags using values of [AccessFlags].
|
||||
* @property returnType The return type compared using [String.startsWith].
|
||||
* @property parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||
* @property opcodes An opcode pattern of the instructions. Wildcard or unknown opcodes can be specified by `null`.
|
||||
* @property strings A list of the strings compared each using [String.contains].
|
||||
* @property customBlock A custom condition for this fingerprint.
|
||||
* @property fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning.
|
||||
*
|
||||
* @constructor Create a new [FingerprintBuilder].
|
||||
*/
|
||||
class FingerprintBuilder internal constructor(
|
||||
private val fuzzyPatternScanThreshold: Int = 0,
|
||||
) {
|
||||
private var accessFlags: Int? = null
|
||||
private var returnType: String? = null
|
||||
private var parameters: List<String>? = null
|
||||
private var opcodes: List<Opcode?>? = null
|
||||
private var strings: List<String>? = null
|
||||
private var customBlock: ((method: Method, classDef: ClassDef) -> Boolean)? = null
|
||||
|
||||
/**
|
||||
* Set the access flags.
|
||||
*
|
||||
* @param accessFlags The exact access flags using values of [AccessFlags].
|
||||
*/
|
||||
fun accessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the access flags.
|
||||
*
|
||||
* @param accessFlags The exact access flags using values of [AccessFlags].
|
||||
*/
|
||||
fun accessFlags(vararg accessFlags: AccessFlags) {
|
||||
this.accessFlags = accessFlags.fold(0) { acc, it -> acc or it.value }
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the return type.
|
||||
*
|
||||
* @param returnType The return type compared using [String.startsWith].
|
||||
*/
|
||||
fun returns(returnType: String) {
|
||||
this.returnType = returnType
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameters.
|
||||
*
|
||||
* @param parameters The parameters of the method. Partial matches allowed and follow the same rules as [returnType].
|
||||
*/
|
||||
fun parameters(vararg parameters: String) {
|
||||
this.parameters = parameters.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the opcodes.
|
||||
*
|
||||
* @param opcodes An opcode pattern of instructions.
|
||||
* Wildcard or unknown opcodes can be specified by `null`.
|
||||
*/
|
||||
fun opcodes(vararg opcodes: Opcode?) {
|
||||
this.opcodes = opcodes.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the opcodes.
|
||||
*
|
||||
* @param instructions A list of instructions or opcode names in SMALI format.
|
||||
* - Wildcard or unknown opcodes can be specified by `null`.
|
||||
* - Empty lines are ignored.
|
||||
* - Each instruction must be on a new line.
|
||||
* - The opcode name is enough, no need to specify the operands.
|
||||
*
|
||||
* @throws Exception If an unknown opcode is used.
|
||||
*/
|
||||
fun opcodes(instructions: String) {
|
||||
this.opcodes = instructions.trimIndent().split("\n").filter {
|
||||
it.isNotBlank()
|
||||
}.map {
|
||||
// Remove any operands.
|
||||
val name = it.split(" ", limit = 1).first().trim()
|
||||
if (name == "null") return@map null
|
||||
|
||||
opcodesByName[name] ?: throw Exception("Unknown opcode: $name")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the strings.
|
||||
*
|
||||
* @param strings A list of strings compared each using [String.contains].
|
||||
*/
|
||||
fun strings(vararg strings: String) {
|
||||
this.strings = strings.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom condition for this fingerprint.
|
||||
*
|
||||
* @param customBlock A custom condition for this fingerprint.
|
||||
*/
|
||||
fun custom(customBlock: (method: Method, classDef: ClassDef) -> Boolean) {
|
||||
this.customBlock = customBlock
|
||||
}
|
||||
|
||||
internal fun build() = Fingerprint(
|
||||
accessFlags,
|
||||
returnType,
|
||||
parameters,
|
||||
opcodes,
|
||||
strings,
|
||||
customBlock,
|
||||
fuzzyPatternScanThreshold,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val opcodesByName = Opcode.entries.associateBy { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [Fingerprint].
|
||||
*
|
||||
* @param fuzzyPatternScanThreshold The threshold for fuzzy pattern scanning. Default is 0.
|
||||
* @param block The block to build the [Fingerprint].
|
||||
*
|
||||
* @return The created [Fingerprint].
|
||||
*/
|
||||
fun fingerprint(
|
||||
fuzzyPatternScanThreshold: Int = 0,
|
||||
block: FingerprintBuilder.() -> Unit,
|
||||
) = FingerprintBuilder(fuzzyPatternScanThreshold).apply(block).build()
|
||||
7
src/main/kotlin/app/revanced/patcher/InternalApi.kt
Normal file
7
src/main/kotlin/app/revanced/patcher/InternalApi.kt
Normal file
@@ -0,0 +1,7 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
@RequiresOptIn(
|
||||
level = RequiresOptIn.Level.ERROR,
|
||||
message = "This is an internal API, don't rely on it.",
|
||||
)
|
||||
annotation class InternalApi
|
||||
16
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
16
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import brut.androlib.apk.ApkInfo
|
||||
|
||||
/**
|
||||
* Metadata about a package.
|
||||
*
|
||||
* @param apkInfo The [ApkInfo] of the apk file.
|
||||
*/
|
||||
class PackageMetadata internal constructor(internal val apkInfo: ApkInfo) {
|
||||
lateinit var packageName: String
|
||||
internal set
|
||||
|
||||
lateinit var packageVersion: String
|
||||
internal set
|
||||
}
|
||||
160
src/main/kotlin/app/revanced/patcher/Patcher.kt
Normal file
160
src/main/kotlin/app/revanced/patcher/Patcher.kt
Normal file
@@ -0,0 +1,160 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import java.io.Closeable
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* A Patcher.
|
||||
*
|
||||
* @param config The configuration to use for the patcher.
|
||||
*/
|
||||
class Patcher(private val config: PatcherConfig) : Closeable {
|
||||
private val logger = Logger.getLogger(this::class.java.name)
|
||||
|
||||
/**
|
||||
* The context containing the current state of the patcher.
|
||||
*/
|
||||
val context = PatcherContext(config)
|
||||
|
||||
init {
|
||||
context.resourceContext.decodeResources(ResourcePatchContext.ResourceMode.NONE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add patches.
|
||||
*
|
||||
* @param patches The patches to add.
|
||||
*/
|
||||
operator fun plusAssign(patches: Set<Patch<*>>) {
|
||||
// Add all patches to the executablePatches set.
|
||||
context.executablePatches += patches
|
||||
|
||||
// Add all patches and their dependencies to the allPatches set.
|
||||
patches.forEach { patch ->
|
||||
fun Patch<*>.addRecursively() =
|
||||
also(context.allPatches::add).dependencies.forEach(Patch<*>::addRecursively)
|
||||
|
||||
patch.addRecursively()
|
||||
}
|
||||
|
||||
context.allPatches.let { allPatches ->
|
||||
// Check, if what kind of resource mode is required.
|
||||
config.resourceMode = if (allPatches.any { patch -> patch.anyRecursively { it is ResourcePatch } }) {
|
||||
ResourcePatchContext.ResourceMode.FULL
|
||||
} else if (allPatches.any { patch -> patch.anyRecursively { it is RawResourcePatch } }) {
|
||||
ResourcePatchContext.ResourceMode.RAW_ONLY
|
||||
} else {
|
||||
ResourcePatchContext.ResourceMode.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute added patches.
|
||||
*
|
||||
* @return A flow of [PatchResult]s.
|
||||
*/
|
||||
operator fun invoke() = flow {
|
||||
fun Patch<*>.execute(
|
||||
executedPatches: LinkedHashMap<Patch<*>, PatchResult>,
|
||||
): PatchResult {
|
||||
// If the patch was executed before or failed, return it's the result.
|
||||
executedPatches[this]?.let { patchResult ->
|
||||
patchResult.exception ?: return patchResult
|
||||
|
||||
return PatchResult(this, PatchException("The patch '$this' failed previously"))
|
||||
}
|
||||
|
||||
// Recursively execute all dependency patches.
|
||||
dependencies.forEach { dependency ->
|
||||
dependency.execute(executedPatches).exception?.let {
|
||||
return PatchResult(
|
||||
this,
|
||||
PatchException(
|
||||
"The patch \"$this\" depends on \"$dependency\", which raised an exception:\n${it.stackTraceToString()}",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the patch.
|
||||
return try {
|
||||
execute(context)
|
||||
|
||||
PatchResult(this)
|
||||
} catch (exception: PatchException) {
|
||||
PatchResult(this, exception)
|
||||
} catch (exception: Exception) {
|
||||
PatchResult(this, PatchException(exception))
|
||||
}.also { executedPatches[this] = it }
|
||||
}
|
||||
|
||||
// Prevent decoding the app manifest twice if it is not needed.
|
||||
if (config.resourceMode != ResourcePatchContext.ResourceMode.NONE) {
|
||||
context.resourceContext.decodeResources(config.resourceMode)
|
||||
}
|
||||
|
||||
logger.info("Initializing lookup maps")
|
||||
|
||||
// Accessing the lazy lookup maps to initialize them.
|
||||
context.bytecodeContext.lookupMaps
|
||||
|
||||
logger.info("Executing patches")
|
||||
|
||||
val executedPatches = LinkedHashMap<Patch<*>, PatchResult>()
|
||||
|
||||
context.executablePatches.sortedBy { it.name }.forEach { patch ->
|
||||
val patchResult = patch.execute(executedPatches)
|
||||
|
||||
// If an exception occurred or the patch has no finalize block, emit the result.
|
||||
if (patchResult.exception != null || patch.finalizeBlock == null) {
|
||||
emit(patchResult)
|
||||
}
|
||||
}
|
||||
|
||||
val succeededPatchesWithFinalizeBlock = executedPatches.values.filter {
|
||||
it.exception == null && it.patch.finalizeBlock != null
|
||||
}
|
||||
|
||||
succeededPatchesWithFinalizeBlock.asReversed().forEach { executionResult ->
|
||||
val patch = executionResult.patch
|
||||
|
||||
val result =
|
||||
try {
|
||||
patch.finalize(context)
|
||||
|
||||
executionResult
|
||||
} catch (exception: PatchException) {
|
||||
PatchResult(patch, exception)
|
||||
} catch (exception: Exception) {
|
||||
PatchResult(patch, PatchException(exception))
|
||||
}
|
||||
|
||||
if (result.exception != null) {
|
||||
emit(
|
||||
PatchResult(
|
||||
patch,
|
||||
PatchException(
|
||||
"The patch \"$patch\" raised an exception: ${result.exception.stackTraceToString()}",
|
||||
result.exception,
|
||||
),
|
||||
),
|
||||
)
|
||||
} else if (patch in context.executablePatches) {
|
||||
emit(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() = context.close()
|
||||
|
||||
/**
|
||||
* Compile and save patched APK files.
|
||||
*
|
||||
* @return The [PatcherResult] containing the patched APK files.
|
||||
*/
|
||||
@OptIn(InternalApi::class)
|
||||
fun get() = PatcherResult(context.bytecodeContext.get(), context.resourceContext.get())
|
||||
}
|
||||
87
src/main/kotlin/app/revanced/patcher/PatcherConfig.kt
Normal file
87
src/main/kotlin/app/revanced/patcher/PatcherConfig.kt
Normal file
@@ -0,0 +1,87 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.ResourcePatchContext
|
||||
import brut.androlib.Config
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* The configuration for the patcher.
|
||||
*
|
||||
* @param apkFile The apk file to patch.
|
||||
* @param temporaryFilesPath A path to a folder to store temporary files in.
|
||||
* @param aaptBinaryPath A path to a custom aapt binary.
|
||||
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
|
||||
*/
|
||||
class PatcherConfig(
|
||||
internal val apkFile: File,
|
||||
private val temporaryFilesPath: File = File("revanced-temporary-files"),
|
||||
aaptBinaryPath: File? = null,
|
||||
frameworkFileDirectory: String? = null,
|
||||
) {
|
||||
/**
|
||||
* The configuration for the patcher.
|
||||
*
|
||||
* @param apkFile The apk file to patch.
|
||||
* @param temporaryFilesPath A path to a folder to store temporary files in.
|
||||
* @param aaptBinaryPath A path to a custom aapt binary.
|
||||
* @param frameworkFileDirectory A path to the directory to cache the framework file in.
|
||||
*/
|
||||
@Deprecated(
|
||||
"Use the constructor with a File for aaptBinaryPath instead.",
|
||||
ReplaceWith("PatcherConfig(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)"),
|
||||
)
|
||||
constructor(
|
||||
apkFile: File,
|
||||
temporaryFilesPath: File = File("revanced-temporary-files"),
|
||||
aaptBinaryPath: String? = null,
|
||||
frameworkFileDirectory: String? = null,
|
||||
) : this(apkFile, temporaryFilesPath, aaptBinaryPath?.let { File(it) }, frameworkFileDirectory)
|
||||
|
||||
private val logger = Logger.getLogger(PatcherConfig::class.java.name)
|
||||
|
||||
/**
|
||||
* The mode to use for resource decoding and compiling.
|
||||
*
|
||||
* @see ResourcePatchContext.ResourceMode
|
||||
*/
|
||||
internal var resourceMode = ResourcePatchContext.ResourceMode.NONE
|
||||
|
||||
/**
|
||||
* The configuration for decoding and compiling resources.
|
||||
*/
|
||||
internal val resourceConfig =
|
||||
Config.getDefaultConfig().apply {
|
||||
aaptBinary = aaptBinaryPath
|
||||
frameworkDirectory = frameworkFileDirectory
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the temporary apk files directory.
|
||||
*/
|
||||
internal val apkFiles = temporaryFilesPath.resolve("apk")
|
||||
|
||||
/**
|
||||
* The path to the temporary patched files directory.
|
||||
*/
|
||||
internal val patchedFiles = temporaryFilesPath.resolve("patched")
|
||||
|
||||
/**
|
||||
* Initialize the temporary files' directories.
|
||||
* This will delete the existing temporary files directory if it exists.
|
||||
*/
|
||||
internal fun initializeTemporaryFilesDirectories() {
|
||||
temporaryFilesPath.apply {
|
||||
if (exists()) {
|
||||
logger.info("Deleting existing temporary files directory")
|
||||
|
||||
if (!deleteRecursively()) {
|
||||
logger.severe("Failed to delete existing temporary files directory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apkFiles.mkdirs()
|
||||
patchedFiles.mkdirs()
|
||||
}
|
||||
}
|
||||
43
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
43
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.ResourcePatchContext
|
||||
import brut.androlib.apk.ApkInfo
|
||||
import brut.directory.ExtFile
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* A context for the patcher containing the current state of the patcher.
|
||||
*
|
||||
* @param config The configuration for the patcher.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class PatcherContext internal constructor(config: PatcherConfig): Closeable {
|
||||
/**
|
||||
* [PackageMetadata] of the supplied [PatcherConfig.apkFile].
|
||||
*/
|
||||
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(config.apkFile)))
|
||||
|
||||
/**
|
||||
* The set of [Patch]es.
|
||||
*/
|
||||
internal val executablePatches = mutableSetOf<Patch<*>>()
|
||||
|
||||
/**
|
||||
* The set of all [Patch]es and their dependencies.
|
||||
*/
|
||||
internal val allPatches = mutableSetOf<Patch<*>>()
|
||||
|
||||
/**
|
||||
* The context for patches containing the current state of the resources.
|
||||
*/
|
||||
internal val resourceContext = ResourcePatchContext(packageMetadata, config)
|
||||
|
||||
/**
|
||||
* The context for patches containing the current state of the bytecode.
|
||||
*/
|
||||
internal val bytecodeContext = BytecodePatchContext(config)
|
||||
|
||||
override fun close() = bytecodeContext.close()
|
||||
}
|
||||
40
src/main/kotlin/app/revanced/patcher/PatcherResult.kt
Normal file
40
src/main/kotlin/app/revanced/patcher/PatcherResult.kt
Normal file
@@ -0,0 +1,40 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* The result of a patcher.
|
||||
*
|
||||
* @param dexFiles The patched dex files.
|
||||
* @param resources The patched resources.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class PatcherResult internal constructor(
|
||||
val dexFiles: Set<PatchedDexFile>,
|
||||
val resources: PatchedResources?,
|
||||
) {
|
||||
|
||||
/**
|
||||
* A dex file.
|
||||
*
|
||||
* @param name The original name of the dex file.
|
||||
* @param stream The dex file as [InputStream].
|
||||
*/
|
||||
class PatchedDexFile internal constructor(val name: String, val stream: InputStream)
|
||||
|
||||
/**
|
||||
* The resources of a patched apk.
|
||||
*
|
||||
* @param resourcesApk The compiled resources.apk file.
|
||||
* @param otherResources The directory containing other resources files.
|
||||
* @param doNotCompress List of files that should not be compressed.
|
||||
* @param deleteResources List of resources that should be deleted.
|
||||
*/
|
||||
class PatchedResources internal constructor(
|
||||
val resourcesApk: File?,
|
||||
val otherResources: File?,
|
||||
val doNotCompress: Set<String>,
|
||||
val deleteResources: Set<String>,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
|
||||
/**
|
||||
* Create a label for the instruction at given index.
|
||||
*
|
||||
* @param index The index to create the label for the instruction at.
|
||||
* @return The label.
|
||||
*/
|
||||
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
|
||||
@@ -0,0 +1,434 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.Label
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.*
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.MethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
|
||||
object InstructionExtensions {
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = instructions.asReversed().forEach { addInstruction(index, it) }
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
* The instructions will be added at the end of the method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
instructions.forEach { addInstruction(it) }
|
||||
|
||||
/**
|
||||
* Remove instructions from a method at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(
|
||||
index: Int,
|
||||
count: Int,
|
||||
) = repeat(count) {
|
||||
removeInstruction(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the first instructions from a method.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index with the given instructions.
|
||||
* The amount of instructions to replace is the amount of instructions in the given list.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethodImplementation.replaceInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) {
|
||||
// Remove the instructions at the given index.
|
||||
removeInstructions(index, instructions.size)
|
||||
|
||||
// Add the instructions at the given index.
|
||||
addInstructions(index, instructions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param instruction The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(
|
||||
index: Int,
|
||||
instruction: BuilderInstruction,
|
||||
) = implementation!!.addInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param instruction The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: BuilderInstruction) = implementation!!.addInstruction(instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
) = implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(smaliInstructions: String) = implementation!!.addInstruction(smaliInstructions.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = implementation!!.addInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) = implementation!!.addInstructions(instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
) = implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(smaliInstructions: String) = implementation!!.addInstructions(smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param smaliInstructions The instructions to add.
|
||||
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
|
||||
*/
|
||||
// Special function for adding instructions with external labels.
|
||||
fun MutableMethod.addInstructionsWithLabels(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
vararg externalLabels: ExternalLabel,
|
||||
) {
|
||||
// Create reference dummy instructions for the instructions.
|
||||
val nopSmali =
|
||||
StringBuilder(smaliInstructions).also { builder ->
|
||||
externalLabels.forEach { (name, _) ->
|
||||
builder.append("\n:$name\nnop")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// Compile the instructions with the dummy labels
|
||||
val compiledInstructions = nopSmali.toInstructions(this)
|
||||
|
||||
// Add the compiled list of instructions to the method.
|
||||
addInstructions(
|
||||
index,
|
||||
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size),
|
||||
)
|
||||
|
||||
implementation!!.apply {
|
||||
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||
// If the compiled instruction is not an offset instruction, skip it.
|
||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||
|
||||
/**
|
||||
* Create a new label for the instruction
|
||||
* and replace it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||
*/
|
||||
fun Instruction.makeNewLabel() {
|
||||
fun replaceOffset(
|
||||
i: BuilderOffsetInstruction,
|
||||
label: Label,
|
||||
): BuilderOffsetInstruction {
|
||||
return when (i) {
|
||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||
is BuilderInstruction22t ->
|
||||
BuilderInstruction22t(
|
||||
i.opcode,
|
||||
i.registerA,
|
||||
i.registerB,
|
||||
label,
|
||||
)
|
||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||
else -> throw IllegalStateException(
|
||||
"A non-offset instruction was given, this should never happen!",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final label.
|
||||
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
|
||||
|
||||
// Create the final instruction with the new label.
|
||||
val newInstruction =
|
||||
replaceOffset(
|
||||
compiledInstruction,
|
||||
label,
|
||||
)
|
||||
|
||||
// Replace the instruction pointing to the dummy label
|
||||
// with the new instruction pointing to the real instruction.
|
||||
replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||
}
|
||||
|
||||
// If the compiled instruction targets its own instruction,
|
||||
// which means it points to some of its own, simply an offset has to be applied.
|
||||
val labelIndex = compiledInstruction.target.location.index
|
||||
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||
this.instructions[index + labelIndex].makeNewLabel()
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Since the compiled instruction points to a dummy instruction,
|
||||
// we can find the real instruction which it was created for by calculation.
|
||||
|
||||
// Get the index of the instruction in the externalLabels list
|
||||
// which the dummy instruction was created for.
|
||||
// This works because we created the dummy instructions in the same order as the externalLabels list.
|
||||
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||
instruction.makeNewLabel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an instruction at the given index.
|
||||
*
|
||||
* @param index The index to remove the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) = implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(
|
||||
index: Int,
|
||||
count: Int,
|
||||
) = implementation!!.removeInstructions(index, count)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(count: Int) = implementation!!.removeInstructions(count)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(
|
||||
index: Int,
|
||||
instruction: BuilderInstruction,
|
||||
) = implementation!!.replaceInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param smaliInstruction The smali instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(
|
||||
index: Int,
|
||||
smaliInstruction: String,
|
||||
) = implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>,
|
||||
) = implementation!!.replaceInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param smaliInstructions The smali instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
) = implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MethodImplementation.getInstruction(index: Int) = instructions.elementAt(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun Method.getInstructionOrNull(index: Int): Instruction? = implementation?.getInstruction(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun Method.getInstruction(index: Int): Instruction = getInstructionOrNull(index)!!
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun <T> Method.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun <T> Method.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun MutableMethod.getInstructionOrNull(index: Int): BuilderInstruction? = implementation?.getInstruction(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = getInstructionOrNull(index)!!
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction or null if the method has no implementation.
|
||||
*/
|
||||
fun <T> MutableMethod.getInstructionOrNull(index: Int): T? = implementation?.getInstruction<T>(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun <T> MutableMethod.getInstruction(index: Int): T = getInstructionOrNull<T>(index)!!
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions or null if the method has no implementation.
|
||||
*/
|
||||
val Method.instructionsOrNull: Iterable<Instruction>? get() = implementation?.instructions
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions.
|
||||
*/
|
||||
val Method.instructions: Iterable<Instruction> get() = instructionsOrNull!!
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions or null if the method has no implementation.
|
||||
*/
|
||||
val MutableMethod.instructionsOrNull: MutableList<BuilderInstruction>? get() = implementation?.instructions
|
||||
|
||||
/**
|
||||
* The instructions of a method.
|
||||
* @return The instructions.
|
||||
*/
|
||||
val MutableMethod.instructions: MutableList<BuilderInstruction> get() = instructionsOrNull!!
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.InternalApi
|
||||
import app.revanced.patcher.PatcherConfig
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
||||
import app.revanced.patcher.util.ClassMerger.merge
|
||||
import app.revanced.patcher.util.MethodNavigator
|
||||
import app.revanced.patcher.util.ProxyClassList
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import lanchon.multidexlib2.RawDexIO
|
||||
import java.io.Closeable
|
||||
import java.io.FileFilter
|
||||
import java.util.*
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* A context for patches containing the current state of the bytecode.
|
||||
*
|
||||
* @param config The [PatcherConfig] used to create this context.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
class BytecodePatchContext internal constructor(private val config: PatcherConfig) :
|
||||
PatchContext<Set<PatcherResult.PatchedDexFile>>,
|
||||
Closeable {
|
||||
private val logger = Logger.getLogger(this::class.java.name)
|
||||
|
||||
/**
|
||||
* [Opcodes] of the supplied [PatcherConfig.apkFile].
|
||||
*/
|
||||
internal val opcodes: Opcodes
|
||||
|
||||
/**
|
||||
* The list of classes.
|
||||
*/
|
||||
val classes = ProxyClassList(
|
||||
MultiDexIO.readDexFile(
|
||||
true,
|
||||
config.apkFile,
|
||||
BasicDexFileNamer(),
|
||||
null,
|
||||
null,
|
||||
).also { opcodes = it.opcodes }.classes.toMutableList(),
|
||||
)
|
||||
|
||||
/**
|
||||
* The lookup maps for methods and the class they are a member of from the [classes].
|
||||
*/
|
||||
internal val lookupMaps by lazy { LookupMaps(classes) }
|
||||
|
||||
/**
|
||||
* Merge the extension of [bytecodePatch] into the [BytecodePatchContext].
|
||||
* If no extension is present, the function will return early.
|
||||
*
|
||||
* @param bytecodePatch The [BytecodePatch] to merge the extension of.
|
||||
*/
|
||||
internal fun mergeExtension(bytecodePatch: BytecodePatch) {
|
||||
bytecodePatch.extensionInputStream?.get()?.use { extensionStream ->
|
||||
RawDexIO.readRawDexFile(extensionStream, 0, null).classes.forEach { classDef ->
|
||||
val existingClass = lookupMaps.classesByType[classDef.type] ?: run {
|
||||
logger.fine { "Adding class \"$classDef\"" }
|
||||
|
||||
classes += classDef
|
||||
lookupMaps.classesByType[classDef.type] = classDef
|
||||
|
||||
return@forEach
|
||||
}
|
||||
|
||||
logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
|
||||
|
||||
existingClass.merge(classDef, this@BytecodePatchContext).let { mergedClass ->
|
||||
// If the class was merged, replace the original class with the merged class.
|
||||
if (mergedClass === existingClass) {
|
||||
return@let
|
||||
}
|
||||
|
||||
classes -= existingClass
|
||||
classes += mergedClass
|
||||
}
|
||||
}
|
||||
} ?: logger.fine("No extension to merge")
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a class with a predicate.
|
||||
*
|
||||
* @param predicate A predicate to match the class.
|
||||
* @return A proxy for the first class that matches the predicate.
|
||||
*/
|
||||
fun classBy(predicate: (ClassDef) -> Boolean) =
|
||||
classes.proxyPool.find { predicate(it.immutableClass) } ?: classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
/**
|
||||
* Proxy the class to allow mutation.
|
||||
*
|
||||
* @param classDef The class to proxy.
|
||||
*
|
||||
* @return A proxy for the class.
|
||||
*/
|
||||
fun proxy(classDef: ClassDef) = classes.proxyPool.find {
|
||||
it.immutableClass.type == classDef.type
|
||||
} ?: ClassProxy(classDef).also { classes.proxyPool.add(it) }
|
||||
|
||||
/**
|
||||
* Navigate a method.
|
||||
*
|
||||
* @param method The method to navigate.
|
||||
*
|
||||
* @return A [MethodNavigator] for the method.
|
||||
*/
|
||||
fun navigate(method: MethodReference) = MethodNavigator(method)
|
||||
|
||||
/**
|
||||
* Compile bytecode from the [BytecodePatchContext].
|
||||
*
|
||||
* @return The compiled bytecode.
|
||||
*/
|
||||
@InternalApi
|
||||
override fun get(): Set<PatcherResult.PatchedDexFile> {
|
||||
logger.info("Compiling patched dex files")
|
||||
|
||||
// Free up memory before compiling the dex files.
|
||||
lookupMaps.close()
|
||||
|
||||
val patchedDexFileResults =
|
||||
config.patchedFiles.resolve("dex").also {
|
||||
it.deleteRecursively() // Make sure the directory is empty.
|
||||
it.mkdirs()
|
||||
}.apply {
|
||||
MultiDexIO.writeDexFile(
|
||||
true,
|
||||
-1,
|
||||
this,
|
||||
BasicDexFileNamer(),
|
||||
object : DexFile {
|
||||
override fun getClasses() =
|
||||
this@BytecodePatchContext.classes.also(ProxyClassList::replaceClasses).toSet()
|
||||
|
||||
override fun getOpcodes() = this@BytecodePatchContext.opcodes
|
||||
},
|
||||
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
|
||||
) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
|
||||
}.listFiles(FileFilter { it.isFile })!!.map {
|
||||
PatcherResult.PatchedDexFile(it.name, it.inputStream())
|
||||
}.toSet()
|
||||
|
||||
System.gc()
|
||||
|
||||
return patchedDexFileResults
|
||||
}
|
||||
|
||||
/**
|
||||
* A lookup map for methods and the class they are a member of and classes.
|
||||
*
|
||||
* @param classes The list of classes to create the lookup maps from.
|
||||
*/
|
||||
internal class LookupMaps internal constructor(classes: List<ClassDef>) : Closeable {
|
||||
/**
|
||||
* Methods associated by strings referenced in it.
|
||||
*/
|
||||
internal val methodsByStrings = MethodClassPairsLookupMap()
|
||||
|
||||
// Lookup map for fast checking if a class exists by its type.
|
||||
val classesByType = mutableMapOf<String, ClassDef>().apply {
|
||||
classes.forEach { classDef -> put(classDef.type, classDef) }
|
||||
}
|
||||
|
||||
init {
|
||||
classes.forEach { classDef ->
|
||||
classDef.methods.forEach { method ->
|
||||
val methodClassPair: MethodClassPair = method to classDef
|
||||
|
||||
// Add strings contained in the method as the key.
|
||||
method.instructionsOrNull?.forEach instructions@{ instruction ->
|
||||
if (instruction.opcode != Opcode.CONST_STRING && instruction.opcode != Opcode.CONST_STRING_JUMBO) {
|
||||
return@instructions
|
||||
}
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
|
||||
methodsByStrings[string] = methodClassPair
|
||||
}
|
||||
|
||||
// In the future, the class type could be added to the lookup map.
|
||||
// This would require MethodFingerprint to be changed to include the class type.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
methodsByStrings.clear()
|
||||
classesByType.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
lookupMaps.close()
|
||||
classes.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of a [Method] and the [ClassDef] it is a member of.
|
||||
*/
|
||||
internal typealias MethodClassPair = Pair<Method, ClassDef>
|
||||
|
||||
/**
|
||||
* A list of [MethodClassPair]s.
|
||||
*/
|
||||
internal typealias MethodClassPairs = LinkedList<MethodClassPair>
|
||||
|
||||
/**
|
||||
* A lookup map for [MethodClassPairs]s.
|
||||
* The key is a string and the value is a list of [MethodClassPair]s.
|
||||
*/
|
||||
internal class MethodClassPairsLookupMap : MutableMap<String, MethodClassPairs> by mutableMapOf() {
|
||||
/**
|
||||
* Add a [MethodClassPair] associated by any key.
|
||||
* If the key does not exist, a new list is created and the [MethodClassPair] is added to it.
|
||||
*/
|
||||
internal operator fun set(key: String, methodClassPair: MethodClassPair) =
|
||||
apply { getOrPut(key) { MethodClassPairs() }.add(methodClassPair) }
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* An option.
|
||||
*
|
||||
* @param T The value type of the option.
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param type The type of the option value (to handle type erasure).
|
||||
@@ -24,15 +22,49 @@ import kotlin.reflect.typeOf
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
class Option<T>
|
||||
@PublishedApi
|
||||
@Deprecated("Use the constructor with the name instead of a key instead.")
|
||||
internal constructor(
|
||||
val name: String,
|
||||
@Deprecated("Use the name property instead.")
|
||||
val key: String,
|
||||
val default: T? = null,
|
||||
val values: Map<String, T?>? = null,
|
||||
@Deprecated("Use the name property instead.")
|
||||
val title: String? = null,
|
||||
val description: String? = null,
|
||||
val required: Boolean = false,
|
||||
val type: KType,
|
||||
val validator: Option<T>.(T?) -> Boolean = { true },
|
||||
) {
|
||||
/**
|
||||
* The name.
|
||||
*/
|
||||
val name = key
|
||||
|
||||
/**
|
||||
* An option.
|
||||
*
|
||||
* @param T The value type of the option.
|
||||
* @param name The name.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param type The type of the option value (to handle type erasure).
|
||||
* @param validator The function to validate the option value.
|
||||
*
|
||||
* @constructor Create a new [Option].
|
||||
*/
|
||||
@PublishedApi
|
||||
internal constructor(
|
||||
name: String,
|
||||
default: T? = null,
|
||||
values: Map<String, T?>? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
type: KType,
|
||||
validator: Option<T>.(T?) -> Boolean = { true },
|
||||
) : this(name, default, values, name, description, required, type, validator)
|
||||
|
||||
/**
|
||||
* The value of the [Option].
|
||||
*/
|
||||
@@ -51,6 +83,7 @@ internal constructor(
|
||||
|
||||
uncheckedValue = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the [Option].
|
||||
*
|
||||
@@ -116,21 +149,21 @@ class Options internal constructor(
|
||||
/**
|
||||
* Set an option's value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param value The value.
|
||||
*
|
||||
* @throws OptionException.OptionNotFoundException If the option does not exist.
|
||||
*/
|
||||
operator fun <T : Any> set(name: String, value: T?) {
|
||||
val option = this[name]
|
||||
operator fun <T : Any> set(key: String, value: T?) {
|
||||
val option = this[key]
|
||||
|
||||
try {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(option as Option<T>).value = value
|
||||
} catch (e: ClassCastException) {
|
||||
throw OptionException.InvalidValueTypeException(
|
||||
value?.let { it::class.jvmName } ?: "null",
|
||||
option.value?.let { it::class.jvmName } ?: "null",
|
||||
value?.let { it::class.java.name } ?: "null",
|
||||
option.value?.let { it::class.java.name } ?: "null",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -138,7 +171,7 @@ class Options internal constructor(
|
||||
/**
|
||||
* Get an option.
|
||||
*
|
||||
* @param key The name.
|
||||
* @param key The key.
|
||||
*
|
||||
* @return The option.
|
||||
*/
|
||||
@@ -148,9 +181,10 @@ class Options internal constructor(
|
||||
/**
|
||||
* Create a new [Option] with a string value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -160,16 +194,18 @@ class Options internal constructor(
|
||||
* @see Option
|
||||
*/
|
||||
fun stringOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: String? = null,
|
||||
values: Map<String, String?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<String>.(String?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -178,9 +214,10 @@ fun stringOption(
|
||||
/**
|
||||
* Create a new [Option] with a string value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -190,16 +227,18 @@ fun stringOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.stringOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: String? = null,
|
||||
values: Map<String, String?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<String>.(String?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -208,9 +247,10 @@ fun PatchBuilder<*>.stringOption(
|
||||
/**
|
||||
* Create a new [Option] with an integer value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -220,16 +260,18 @@ fun PatchBuilder<*>.stringOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun intOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Int? = null,
|
||||
values: Map<String, Int?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Int>.(Int?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -238,9 +280,10 @@ fun intOption(
|
||||
/**
|
||||
* Create a new [Option] with an integer value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -250,16 +293,18 @@ fun intOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.intOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Int? = null,
|
||||
values: Map<String, Int?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Int>.(Int?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -268,9 +313,10 @@ fun PatchBuilder<*>.intOption(
|
||||
/**
|
||||
* Create a new [Option] with a boolean value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -280,16 +326,18 @@ fun PatchBuilder<*>.intOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun booleanOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Boolean? = null,
|
||||
values: Map<String, Boolean?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -298,9 +346,10 @@ fun booleanOption(
|
||||
/**
|
||||
* Create a new [Option] with a boolean value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -310,16 +359,18 @@ fun booleanOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.booleanOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Boolean? = null,
|
||||
values: Map<String, Boolean?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Boolean>.(Boolean?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -328,9 +379,10 @@ fun PatchBuilder<*>.booleanOption(
|
||||
/**
|
||||
* Create a new [Option] with a float value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -340,16 +392,18 @@ fun PatchBuilder<*>.booleanOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun floatOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Float? = null,
|
||||
values: Map<String, Float?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Float>.(Float?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -358,9 +412,10 @@ fun floatOption(
|
||||
/**
|
||||
* Create a new [Option] with a float value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -370,16 +425,18 @@ fun floatOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.floatOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Float? = null,
|
||||
values: Map<String, Float?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Float>.(Float?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -388,9 +445,10 @@ fun PatchBuilder<*>.floatOption(
|
||||
/**
|
||||
* Create a new [Option] with a long value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -400,16 +458,18 @@ fun PatchBuilder<*>.floatOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun longOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Long? = null,
|
||||
values: Map<String, Long?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Long>.(Long?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -418,9 +478,10 @@ fun longOption(
|
||||
/**
|
||||
* Create a new [Option] with a long value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -430,16 +491,18 @@ fun longOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.longOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: Long? = null,
|
||||
values: Map<String, Long?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<Long>.(Long?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -448,9 +511,10 @@ fun PatchBuilder<*>.longOption(
|
||||
/**
|
||||
* Create a new [Option] with a string list value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -460,16 +524,18 @@ fun PatchBuilder<*>.longOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun stringsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<String>? = null,
|
||||
values: Map<String, List<String>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<String>>.(List<String>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -478,9 +544,10 @@ fun stringsOption(
|
||||
/**
|
||||
* Create a new [Option] with a string list value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -490,16 +557,18 @@ fun stringsOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.stringsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<String>? = null,
|
||||
values: Map<String, List<String>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<String>>.(List<String>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -508,9 +577,10 @@ fun PatchBuilder<*>.stringsOption(
|
||||
/**
|
||||
* Create a new [Option] with an integer list value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -520,16 +590,18 @@ fun PatchBuilder<*>.stringsOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun intsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Int>? = null,
|
||||
values: Map<String, List<Int>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Int>>.(List<Int>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -538,9 +610,10 @@ fun intsOption(
|
||||
/**
|
||||
* Create a new [Option] with an integer list value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -550,16 +623,18 @@ fun intsOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.intsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Int>? = null,
|
||||
values: Map<String, List<Int>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Int>>.(List<Int>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -568,9 +643,10 @@ fun PatchBuilder<*>.intsOption(
|
||||
/**
|
||||
* Create a new [Option] with a boolean list value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -580,16 +656,18 @@ fun PatchBuilder<*>.intsOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun booleansOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Boolean>? = null,
|
||||
values: Map<String, List<Boolean>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Boolean>>.(List<Boolean>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -598,9 +676,10 @@ fun booleansOption(
|
||||
/**
|
||||
* Create a new [Option] with a boolean list value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -610,16 +689,18 @@ fun booleansOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.booleansOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Boolean>? = null,
|
||||
values: Map<String, List<Boolean>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Boolean>>.(List<Boolean>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -628,9 +709,10 @@ fun PatchBuilder<*>.booleansOption(
|
||||
/**
|
||||
* Create a new [Option] with a float list value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -640,16 +722,18 @@ fun PatchBuilder<*>.booleansOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.floatsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Float>? = null,
|
||||
values: Map<String, List<Float>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Float>>.(List<Float>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -658,9 +742,10 @@ fun PatchBuilder<*>.floatsOption(
|
||||
/**
|
||||
* Create a new [Option] with a long list value.
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -670,16 +755,18 @@ fun PatchBuilder<*>.floatsOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun longsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Long>? = null,
|
||||
values: Map<String, List<Long>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Long>>.(List<Long>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -688,9 +775,10 @@ fun longsOption(
|
||||
/**
|
||||
* Create a new [Option] with a long list value and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -700,16 +788,18 @@ fun longsOption(
|
||||
* @see Option
|
||||
*/
|
||||
fun PatchBuilder<*>.longsOption(
|
||||
name: String,
|
||||
key: String,
|
||||
default: List<Long>? = null,
|
||||
values: Map<String, List<Long>?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
validator: Option<List<Long>>.(List<Long>?) -> Boolean = { true },
|
||||
) = option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -718,9 +808,10 @@ fun PatchBuilder<*>.longsOption(
|
||||
/**
|
||||
* Create a new [Option].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -730,16 +821,18 @@ fun PatchBuilder<*>.longsOption(
|
||||
* @see Option
|
||||
*/
|
||||
inline fun <reified T> option(
|
||||
name: String,
|
||||
key: String,
|
||||
default: T? = null,
|
||||
values: Map<String, T?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
noinline validator: Option<T>.(T?) -> Boolean = { true },
|
||||
) = Option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
typeOf<T>(),
|
||||
@@ -749,9 +842,10 @@ inline fun <reified T> option(
|
||||
/**
|
||||
* Create a new [Option] and add it to the current [PatchBuilder].
|
||||
*
|
||||
* @param name The name.
|
||||
* @param key The key.
|
||||
* @param default The default value.
|
||||
* @param values Eligible option values mapped to a human-readable name.
|
||||
* @param title The title.
|
||||
* @param description A description.
|
||||
* @param required Whether the option is required.
|
||||
* @param validator The function to validate the option value.
|
||||
@@ -761,16 +855,18 @@ inline fun <reified T> option(
|
||||
* @see Option
|
||||
*/
|
||||
inline fun <reified T> PatchBuilder<*>.option(
|
||||
name: String,
|
||||
key: String,
|
||||
default: T? = null,
|
||||
values: Map<String, T?>? = null,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
required: Boolean = false,
|
||||
noinline validator: Option<T>.(T?) -> Boolean = { true },
|
||||
) = app.revanced.patcher.patch.option(
|
||||
name,
|
||||
key,
|
||||
default,
|
||||
values,
|
||||
title,
|
||||
description,
|
||||
required,
|
||||
validator,
|
||||
@@ -788,29 +884,26 @@ sealed class OptionException(errorMessage: String) : Exception(errorMessage, nul
|
||||
* @param invalidType The type of the value that was passed.
|
||||
* @param expectedType The type of the value that was expected.
|
||||
*/
|
||||
class InvalidValueTypeException(invalidType: String, expectedType: String) :
|
||||
OptionException("Type $expectedType was expected but received type $invalidType")
|
||||
class InvalidValueTypeException(invalidType: String, expectedType: String) : OptionException("Type $expectedType was expected but received type $invalidType")
|
||||
|
||||
/**
|
||||
* An exception thrown when a value did not satisfy the value conditions specified by the [Option].
|
||||
*
|
||||
* @param value The value that failed validation.
|
||||
*/
|
||||
class ValueValidationException(value: Any?, option: Option<*>) :
|
||||
OptionException("The option value \"$value\" failed validation for ${option.name}")
|
||||
class ValueValidationException(value: Any?, option: Option<*>) : OptionException("The option value \"$value\" failed validation for ${option.name}")
|
||||
|
||||
/**
|
||||
* An exception thrown when a value is required but null was passed.
|
||||
*
|
||||
* @param option The [Option] that requires a value.
|
||||
*/
|
||||
class ValueRequiredException(option: Option<*>) :
|
||||
OptionException("The option ${option.name} requires a value, but the value was null")
|
||||
class ValueRequiredException(option: Option<*>) : OptionException("The option ${option.name} requires a value, but the value was null")
|
||||
|
||||
/**
|
||||
* An exception thrown when a [Option] is not found.
|
||||
*
|
||||
* @param name The name of the [Option].
|
||||
* @param key The key of the [Option].
|
||||
*/
|
||||
class OptionNotFoundException(name: String) : OptionException("No option with name $name")
|
||||
class OptionNotFoundException(key: String) : OptionException("No option with key $key")
|
||||
}
|
||||
687
src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Normal file
687
src/main/kotlin/app/revanced/patcher/patch/Patch.kt
Normal file
@@ -0,0 +1,687 @@
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.PatcherContext
|
||||
import dalvik.system.DexClassLoader
|
||||
import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.lang.reflect.Member
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Modifier
|
||||
import java.net.URLClassLoader
|
||||
import java.util.function.Supplier
|
||||
import java.util.jar.JarFile
|
||||
|
||||
typealias PackageName = String
|
||||
typealias VersionName = String
|
||||
typealias Package = Pair<PackageName, Set<VersionName>?>
|
||||
|
||||
/**
|
||||
* A patch.
|
||||
*
|
||||
* @param C The [PatchContext] to execute and finalize the patch with.
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param dependencies Other patches this patch depends on.
|
||||
* @param compatiblePackages The packages the patch is compatible with.
|
||||
* If null, the patch is compatible with all packages.
|
||||
* @param options The options of the patch.
|
||||
* @param executeBlock The execution block of the patch.
|
||||
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
||||
* in reverse order of execution.
|
||||
*
|
||||
* @constructor Create a new patch.
|
||||
*/
|
||||
sealed class Patch<C : PatchContext<*>>(
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val use: Boolean,
|
||||
val dependencies: Set<Patch<*>>,
|
||||
val compatiblePackages: Set<Package>?,
|
||||
options: Set<Option<*>>,
|
||||
private val executeBlock: (C) -> Unit,
|
||||
// Must be internal and nullable, so that Patcher.invoke can check,
|
||||
// if a patch has a finalizing block in order to not emit it twice.
|
||||
internal var finalizeBlock: ((C) -> Unit)?,
|
||||
) {
|
||||
/**
|
||||
* The options of the patch.
|
||||
*/
|
||||
val options = Options(options)
|
||||
|
||||
/**
|
||||
* Calls the execution block of the patch.
|
||||
* This function is called by [Patcher.invoke].
|
||||
*
|
||||
* @param context The [PatcherContext] to get the [PatchContext] from to execute the patch with.
|
||||
*/
|
||||
internal abstract fun execute(context: PatcherContext)
|
||||
|
||||
/**
|
||||
* Calls the execution block of the patch.
|
||||
*
|
||||
* @param context The [PatchContext] to execute the patch with.
|
||||
*/
|
||||
fun execute(context: C) = executeBlock(context)
|
||||
|
||||
/**
|
||||
* Calls the finalizing block of the patch.
|
||||
* This function is called by [Patcher.invoke].
|
||||
*
|
||||
* @param context The [PatcherContext] to get the [PatchContext] from to finalize the patch with.
|
||||
*/
|
||||
internal abstract fun finalize(context: PatcherContext)
|
||||
|
||||
/**
|
||||
* Calls the finalizing block of the patch.
|
||||
*
|
||||
* @param context The [PatchContext] to finalize the patch with.
|
||||
*/
|
||||
fun finalize(context: C) {
|
||||
finalizeBlock?.invoke(context)
|
||||
}
|
||||
|
||||
override fun toString() = name ?:
|
||||
"Patch@${System.identityHashCode(this)}"
|
||||
}
|
||||
|
||||
internal fun Patch<*>.anyRecursively(
|
||||
visited: MutableSet<Patch<*>> = mutableSetOf(),
|
||||
predicate: (Patch<*>) -> Boolean,
|
||||
): Boolean {
|
||||
if (this in visited) return false
|
||||
|
||||
if (predicate(this)) return true
|
||||
|
||||
visited += this
|
||||
|
||||
return dependencies.any { it.anyRecursively(visited, predicate) }
|
||||
}
|
||||
|
||||
internal fun Iterable<Patch<*>>.forEachRecursively(
|
||||
visited: MutableSet<Patch<*>> = mutableSetOf(),
|
||||
action: (Patch<*>) -> Unit,
|
||||
): Unit = forEach {
|
||||
if (it in visited) return@forEach
|
||||
|
||||
visited += it
|
||||
action(it)
|
||||
|
||||
it.dependencies.forEachRecursively(visited, action)
|
||||
}
|
||||
|
||||
/**
|
||||
* A bytecode patch.
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param compatiblePackages The packages the patch is compatible with.
|
||||
* If null, the patch is compatible with all packages.
|
||||
* @param dependencies Other patches this patch depends on.
|
||||
* @param options The options of the patch.
|
||||
* @property extensionInputStream Getter for the extension input stream of the patch.
|
||||
* An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
|
||||
* @param executeBlock The execution block of the patch.
|
||||
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
||||
* in reverse order of execution.
|
||||
*
|
||||
* @constructor Create a new bytecode patch.
|
||||
*/
|
||||
class BytecodePatch internal constructor(
|
||||
name: String?,
|
||||
description: String?,
|
||||
use: Boolean,
|
||||
compatiblePackages: Set<Package>?,
|
||||
dependencies: Set<Patch<*>>,
|
||||
options: Set<Option<*>>,
|
||||
val extensionInputStream: Supplier<InputStream>?,
|
||||
executeBlock: (BytecodePatchContext) -> Unit,
|
||||
finalizeBlock: ((BytecodePatchContext) -> Unit)?,
|
||||
) : Patch<BytecodePatchContext>(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
dependencies,
|
||||
compatiblePackages,
|
||||
options,
|
||||
executeBlock,
|
||||
finalizeBlock,
|
||||
) {
|
||||
override fun execute(context: PatcherContext) = with(context.bytecodeContext) {
|
||||
mergeExtension(this@BytecodePatch)
|
||||
execute(this)
|
||||
}
|
||||
|
||||
override fun finalize(context: PatcherContext) = finalize(context.bytecodeContext)
|
||||
|
||||
override fun toString() = name ?: "Bytecode${super.toString()}"
|
||||
}
|
||||
|
||||
/**
|
||||
* A raw resource patch.
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param compatiblePackages The packages the patch is compatible with.
|
||||
* If null, the patch is compatible with all packages.
|
||||
* @param dependencies Other patches this patch depends on.
|
||||
* @param options The options of the patch.
|
||||
* @param executeBlock The execution block of the patch.
|
||||
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
||||
* in reverse order of execution.
|
||||
*
|
||||
* @constructor Create a new raw resource patch.
|
||||
*/
|
||||
class RawResourcePatch internal constructor(
|
||||
name: String?,
|
||||
description: String?,
|
||||
use: Boolean,
|
||||
compatiblePackages: Set<Package>?,
|
||||
dependencies: Set<Patch<*>>,
|
||||
options: Set<Option<*>>,
|
||||
executeBlock: (ResourcePatchContext) -> Unit,
|
||||
finalizeBlock: ((ResourcePatchContext) -> Unit)?,
|
||||
) : Patch<ResourcePatchContext>(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
dependencies,
|
||||
compatiblePackages,
|
||||
options,
|
||||
executeBlock,
|
||||
finalizeBlock,
|
||||
) {
|
||||
override fun execute(context: PatcherContext) = execute(context.resourceContext)
|
||||
|
||||
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
||||
|
||||
override fun toString() = name ?: "RawResource${super.toString()}"
|
||||
}
|
||||
|
||||
/**
|
||||
* A resource patch.
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param compatiblePackages The packages the patch is compatible with.
|
||||
* If null, the patch is compatible with all packages.
|
||||
* @param dependencies Other patches this patch depends on.
|
||||
* @param options The options of the patch.
|
||||
* @param executeBlock The execution block of the patch.
|
||||
* @param finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
||||
* in reverse order of execution.
|
||||
*
|
||||
* @constructor Create a new resource patch.
|
||||
*/
|
||||
class ResourcePatch internal constructor(
|
||||
name: String?,
|
||||
description: String?,
|
||||
use: Boolean,
|
||||
compatiblePackages: Set<Package>?,
|
||||
dependencies: Set<Patch<*>>,
|
||||
options: Set<Option<*>>,
|
||||
executeBlock: (ResourcePatchContext) -> Unit,
|
||||
finalizeBlock: ((ResourcePatchContext) -> Unit)?,
|
||||
) : Patch<ResourcePatchContext>(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
dependencies,
|
||||
compatiblePackages,
|
||||
options,
|
||||
executeBlock,
|
||||
finalizeBlock,
|
||||
) {
|
||||
override fun execute(context: PatcherContext) = execute(context.resourceContext)
|
||||
|
||||
override fun finalize(context: PatcherContext) = finalize(context.resourceContext)
|
||||
|
||||
override fun toString() = name ?: "Resource${super.toString()}"
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Patch] builder.
|
||||
*
|
||||
* @param C The [PatchContext] to execute and finalize the patch with.
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @property compatiblePackages The packages the patch is compatible with.
|
||||
* If null, the patch is compatible with all packages.
|
||||
* @property dependencies Other patches this patch depends on.
|
||||
* @property options The options of the patch.
|
||||
* @property executionBlock The execution block of the patch.
|
||||
* @property finalizeBlock The finalizing block of the patch. Called after all patches have been executed,
|
||||
* in reverse order of execution.
|
||||
*
|
||||
* @constructor Create a new [Patch] builder.
|
||||
*/
|
||||
sealed class PatchBuilder<C : PatchContext<*>>(
|
||||
protected val name: String?,
|
||||
protected val description: String?,
|
||||
protected val use: Boolean,
|
||||
) {
|
||||
protected var compatiblePackages: MutableSet<Package>? = null
|
||||
protected var dependencies = mutableSetOf<Patch<*>>()
|
||||
protected val options = mutableSetOf<Option<*>>()
|
||||
|
||||
protected var executionBlock: ((C) -> Unit) = { }
|
||||
protected var finalizeBlock: ((C) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Add an option to the patch.
|
||||
*
|
||||
* @return The added option.
|
||||
*/
|
||||
operator fun <T> Option<T>.invoke() = apply {
|
||||
options += this
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a package a patch is compatible with.
|
||||
*
|
||||
* @param versions The versions of the package.
|
||||
*/
|
||||
operator fun String.invoke(vararg versions: String) = invoke(versions.toSet())
|
||||
|
||||
/**
|
||||
* Create a package a patch is compatible with.
|
||||
*
|
||||
* @param versions The versions of the package.
|
||||
*/
|
||||
private operator fun String.invoke(versions: Set<String>? = null) = this to versions
|
||||
|
||||
/**
|
||||
* Add packages the patch is compatible with.
|
||||
*
|
||||
* @param packages The packages the patch is compatible with.
|
||||
*/
|
||||
fun compatibleWith(vararg packages: Package) {
|
||||
if (compatiblePackages == null) {
|
||||
compatiblePackages = mutableSetOf()
|
||||
}
|
||||
|
||||
compatiblePackages!! += packages
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the compatible packages of the patch.
|
||||
*
|
||||
* @param packages The packages the patch is compatible with.
|
||||
*/
|
||||
fun compatibleWith(vararg packages: String) = compatibleWith(*packages.map { it() }.toTypedArray())
|
||||
|
||||
/**
|
||||
* Add dependencies to the patch.
|
||||
*
|
||||
* @param patches The patches the patch depends on.
|
||||
*/
|
||||
fun dependsOn(vararg patches: Patch<*>) {
|
||||
dependencies += patches
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the execution block of the patch.
|
||||
*
|
||||
* @param block The execution block of the patch.
|
||||
*/
|
||||
fun execute(block: C.() -> Unit) {
|
||||
executionBlock = block
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the finalizing block of the patch.
|
||||
*
|
||||
* @param block The finalizing block of the patch.
|
||||
*/
|
||||
fun finalize(block: C.() -> Unit) {
|
||||
finalizeBlock = block
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the patch.
|
||||
*
|
||||
* @return The built patch.
|
||||
*/
|
||||
internal abstract fun build(): Patch<C>
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a [Patch].
|
||||
*
|
||||
* @param B The [PatchBuilder] to build the patch with.
|
||||
* @param block The block to build the patch.
|
||||
*
|
||||
* @return The built [Patch].
|
||||
*/
|
||||
private fun <B : PatchBuilder<*>> B.buildPatch(block: B.() -> Unit = {}) = apply(block).build()
|
||||
|
||||
/**
|
||||
* A [BytecodePatchBuilder] builder.
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @property extensionInputStream Getter for the extension input stream of the patch.
|
||||
* An extension is a precompiled DEX file that is merged into the patched app before this patch is executed.
|
||||
*
|
||||
* @constructor Create a new [BytecodePatchBuilder] builder.
|
||||
*/
|
||||
class BytecodePatchBuilder internal constructor(
|
||||
name: String?,
|
||||
description: String?,
|
||||
use: Boolean,
|
||||
) : PatchBuilder<BytecodePatchContext>(name, description, use) {
|
||||
// Must be internal for the inlined function "extendWith".
|
||||
@PublishedApi
|
||||
internal var extensionInputStream: Supplier<InputStream>? = null
|
||||
|
||||
// Inlining is necessary to get the class loader that loaded the patch
|
||||
// to load the extension from the resources.
|
||||
/**
|
||||
* Set the extension of the patch.
|
||||
*
|
||||
* @param extension The name of the extension resource.
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun extendWith(extension: String) = apply {
|
||||
val classLoader = object {}.javaClass.classLoader
|
||||
|
||||
extensionInputStream = Supplier {
|
||||
classLoader.getResourceAsStream(extension) ?: throw PatchException("Extension \"$extension\" not found")
|
||||
}
|
||||
}
|
||||
|
||||
override fun build() = BytecodePatch(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
compatiblePackages,
|
||||
dependencies,
|
||||
options,
|
||||
extensionInputStream,
|
||||
executionBlock,
|
||||
finalizeBlock,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [BytecodePatch].
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param block The block to build the patch.
|
||||
*
|
||||
* @return The created [BytecodePatch].
|
||||
*/
|
||||
fun bytecodePatch(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
) = BytecodePatchBuilder(name, description, use).buildPatch(block) as BytecodePatch
|
||||
|
||||
/**
|
||||
* A [RawResourcePatch] builder.
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
*
|
||||
* @constructor Create a new [RawResourcePatch] builder.
|
||||
*/
|
||||
class RawResourcePatchBuilder internal constructor(
|
||||
name: String?,
|
||||
description: String?,
|
||||
use: Boolean,
|
||||
) : PatchBuilder<ResourcePatchContext>(name, description, use) {
|
||||
override fun build() = RawResourcePatch(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
compatiblePackages,
|
||||
dependencies,
|
||||
options,
|
||||
executionBlock,
|
||||
finalizeBlock,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [RawResourcePatch].
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param block The block to build the patch.
|
||||
* @return The created [RawResourcePatch].
|
||||
*/
|
||||
fun rawResourcePatch(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: RawResourcePatchBuilder.() -> Unit = {},
|
||||
) = RawResourcePatchBuilder(name, description, use).buildPatch(block) as RawResourcePatch
|
||||
|
||||
/**
|
||||
* A [ResourcePatch] builder.
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
*
|
||||
* @constructor Create a new [ResourcePatch] builder.
|
||||
*/
|
||||
class ResourcePatchBuilder internal constructor(
|
||||
name: String?,
|
||||
description: String?,
|
||||
use: Boolean,
|
||||
) : PatchBuilder<ResourcePatchContext>(name, description, use) {
|
||||
override fun build() = ResourcePatch(
|
||||
name,
|
||||
description,
|
||||
use,
|
||||
compatiblePackages,
|
||||
dependencies,
|
||||
options,
|
||||
executionBlock,
|
||||
finalizeBlock,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new [ResourcePatch].
|
||||
*
|
||||
* @param name The name of the patch.
|
||||
* If null, the patch is named "Patch" and will not be loaded by [PatchLoader].
|
||||
* @param description The description of the patch.
|
||||
* @param use Whether or not the patch should be used.
|
||||
* @param block The block to build the patch.
|
||||
*
|
||||
* @return The created [ResourcePatch].
|
||||
*/
|
||||
fun resourcePatch(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
use: Boolean = true,
|
||||
block: ResourcePatchBuilder.() -> Unit = {},
|
||||
) = ResourcePatchBuilder(name, description, use).buildPatch(block) as ResourcePatch
|
||||
|
||||
/**
|
||||
* An exception thrown when patching.
|
||||
*
|
||||
* @param errorMessage The exception message.
|
||||
* @param cause The corresponding [Throwable].
|
||||
*/
|
||||
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
|
||||
constructor(errorMessage: String) : this(errorMessage, null)
|
||||
constructor(cause: Throwable) : this(cause.message, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* A result of executing a [Patch].
|
||||
*
|
||||
* @param patch The [Patch] that was executed.
|
||||
* @param exception The [PatchException] thrown, if any.
|
||||
*/
|
||||
class PatchResult internal constructor(val patch: Patch<*>, val exception: PatchException? = null)
|
||||
|
||||
/**
|
||||
* A loader for patches.
|
||||
*
|
||||
* Loads unnamed patches from JAR or DEX files declared as public static fields
|
||||
* or returned by public static and non-parametrized methods.
|
||||
*
|
||||
* @param byPatchesFile The patches associated by the patches file they were loaded from.
|
||||
*/
|
||||
sealed class PatchLoader private constructor(
|
||||
val byPatchesFile: Map<File, Set<Patch<*>>>,
|
||||
) : Set<Patch<*>> by byPatchesFile.values.flatten().toSet() {
|
||||
/**
|
||||
* @param patchesFiles A set of JAR or DEX files to load the patches from.
|
||||
* @param getBinaryClassNames A function that returns the binary names of all classes accessible by the class loader.
|
||||
* @param classLoader The [ClassLoader] to use for loading the classes.
|
||||
*/
|
||||
private constructor(
|
||||
patchesFiles: Set<File>,
|
||||
getBinaryClassNames: (patchesFile: File) -> List<String>,
|
||||
classLoader: ClassLoader,
|
||||
) : this(classLoader.loadPatches(patchesFiles.associateWith { getBinaryClassNames(it).toSet() }))
|
||||
|
||||
/**
|
||||
* A [PatchLoader] for JAR files.
|
||||
*
|
||||
* @param patchesFiles The JAR files to load the patches from.
|
||||
*
|
||||
* @constructor Create a new [PatchLoader] for JAR files.
|
||||
*/
|
||||
class Jar(patchesFiles: Set<File>) :
|
||||
PatchLoader(
|
||||
patchesFiles,
|
||||
{ file ->
|
||||
JarFile(file).entries().toList().filter { it.name.endsWith(".class") }
|
||||
.map { it.name.substringBeforeLast('.').replace('/', '.') }
|
||||
},
|
||||
URLClassLoader(patchesFiles.map { it.toURI().toURL() }.toTypedArray()),
|
||||
)
|
||||
|
||||
/**
|
||||
* A [PatchLoader] for [Dex] files.
|
||||
*
|
||||
* @param patchesFiles The DEX files to load the patches from.
|
||||
* @param optimizedDexDirectory The directory to store optimized DEX files in.
|
||||
* This parameter is deprecated and has no effect since API level 26.
|
||||
*
|
||||
* @constructor Create a new [PatchLoader] for [Dex] files.
|
||||
*/
|
||||
class Dex(patchesFiles: Set<File>, optimizedDexDirectory: File? = null) :
|
||||
PatchLoader(
|
||||
patchesFiles,
|
||||
{ patchBundle ->
|
||||
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
|
||||
.map { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1)
|
||||
}
|
||||
},
|
||||
DexClassLoader(
|
||||
patchesFiles.joinToString(File.pathSeparator) { it.absolutePath },
|
||||
optimizedDexDirectory?.absolutePath,
|
||||
null,
|
||||
this::class.java.classLoader,
|
||||
),
|
||||
)
|
||||
|
||||
// Companion object required for unit tests.
|
||||
private companion object {
|
||||
val Class<*>.isPatch get() = Patch::class.java.isAssignableFrom(this)
|
||||
|
||||
/**
|
||||
* Public static fields that are patches.
|
||||
*/
|
||||
private val Class<*>.patchFields
|
||||
get() = fields.filter { field ->
|
||||
field.type.isPatch && field.canAccess()
|
||||
}.map { field ->
|
||||
field.get(null) as Patch<*>
|
||||
}
|
||||
|
||||
/**
|
||||
* Public static and non-parametrized methods that return patches.
|
||||
*/
|
||||
private val Class<*>.patchMethods
|
||||
get() = methods.filter { method ->
|
||||
method.returnType.isPatch && method.parameterCount == 0 && method.canAccess()
|
||||
}.map { method ->
|
||||
method.invoke(null) as Patch<*>
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads unnamed patches declared as public static fields
|
||||
* or returned by public static and non-parametrized methods.
|
||||
*
|
||||
* @param binaryClassNamesByPatchesFile The binary class name of the classes to load the patches from
|
||||
* associated by the patches file.
|
||||
*
|
||||
* @return The loaded patches associated by the patches file.
|
||||
*/
|
||||
private fun ClassLoader.loadPatches(binaryClassNamesByPatchesFile: Map<File, Set<String>>) =
|
||||
binaryClassNamesByPatchesFile.mapValues { (_, binaryClassNames) ->
|
||||
binaryClassNames.asSequence().map {
|
||||
loadClass(it)
|
||||
}.flatMap {
|
||||
it.patchFields + it.patchMethods
|
||||
}.filter {
|
||||
it.name != null
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private fun Member.canAccess(): Boolean {
|
||||
if (this is Method && parameterCount != 0) return false
|
||||
|
||||
return Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads patches from JAR files declared as public static fields
|
||||
* or returned by public static and non-parametrized methods.
|
||||
* Patches with no name are not loaded.
|
||||
*
|
||||
* @param patchesFiles The JAR files to load the patches from.
|
||||
*
|
||||
* @return The loaded patches.
|
||||
*/
|
||||
fun loadPatchesFromJar(patchesFiles: Set<File>) =
|
||||
PatchLoader.Jar(patchesFiles)
|
||||
|
||||
/**
|
||||
* Loads patches from DEX files declared as public static fields
|
||||
* or returned by public static and non-parametrized methods.
|
||||
* Patches with no name are not loaded.
|
||||
*
|
||||
* @param patchesFiles The DEX files to load the patches from.
|
||||
*
|
||||
* @return The loaded patches.
|
||||
*/
|
||||
fun loadPatchesFromDex(patchesFiles: Set<File>, optimizedDexDirectory: File? = null) =
|
||||
PatchLoader.Dex(patchesFiles, optimizedDexDirectory)
|
||||
@@ -0,0 +1,9 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import java.util.function.Supplier
|
||||
|
||||
/**
|
||||
* A common interface for contexts such as [ResourcePatchContext] and [BytecodePatchContext].
|
||||
*/
|
||||
|
||||
sealed interface PatchContext<T> : Supplier<T>
|
||||
@@ -0,0 +1,233 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.InternalApi
|
||||
import app.revanced.patcher.PackageMetadata
|
||||
import app.revanced.patcher.PatcherConfig
|
||||
import app.revanced.patcher.PatcherResult
|
||||
import app.revanced.patcher.util.Document
|
||||
import brut.androlib.AaptInvoker
|
||||
import brut.androlib.ApkDecoder
|
||||
import brut.androlib.apk.UsesFramework
|
||||
import brut.androlib.res.Framework
|
||||
import brut.androlib.res.ResourcesDecoder
|
||||
import brut.androlib.res.decoder.AndroidManifestPullStreamDecoder
|
||||
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||
import brut.androlib.res.xml.ResXmlUtils
|
||||
import brut.directory.ExtFile
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
import java.util.logging.Logger
|
||||
|
||||
/**
|
||||
* A context for patches containing the current state of resources.
|
||||
*
|
||||
* @param packageMetadata The [PackageMetadata] of the apk file.
|
||||
* @param config The [PatcherConfig] used to create this context.
|
||||
*/
|
||||
class ResourcePatchContext internal constructor(
|
||||
private val packageMetadata: PackageMetadata,
|
||||
private val config: PatcherConfig,
|
||||
) : PatchContext<PatcherResult.PatchedResources?> {
|
||||
private val logger = Logger.getLogger(ResourcePatchContext::class.java.name)
|
||||
|
||||
/**
|
||||
* Read a document from an [InputStream].
|
||||
*/
|
||||
fun document(inputStream: InputStream) = Document(inputStream)
|
||||
|
||||
/**
|
||||
* Read and write documents in the [PatcherConfig.apkFiles].
|
||||
*/
|
||||
fun document(path: String) = Document(get(path))
|
||||
|
||||
/**
|
||||
* Set of resources from [PatcherConfig.apkFiles] to delete.
|
||||
*/
|
||||
private val deleteResources = mutableSetOf<String>()
|
||||
|
||||
/**
|
||||
* Decode resources of [PatcherConfig.apkFile].
|
||||
*
|
||||
* @param mode The [ResourceMode] to use.
|
||||
*/
|
||||
internal fun decodeResources(mode: ResourceMode) = with(packageMetadata.apkInfo) {
|
||||
config.initializeTemporaryFilesDirectories()
|
||||
|
||||
// Needed to decode resources.
|
||||
val resourcesDecoder = ResourcesDecoder(config.resourceConfig, this)
|
||||
|
||||
if (mode == ResourceMode.FULL) {
|
||||
logger.info("Decoding resources")
|
||||
|
||||
resourcesDecoder.decodeResources(config.apkFiles)
|
||||
resourcesDecoder.decodeManifest(config.apkFiles)
|
||||
|
||||
// Needed to record uncompressed files.
|
||||
ApkDecoder(this, config.resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
||||
|
||||
usesFramework =
|
||||
UsesFramework().apply {
|
||||
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||
}
|
||||
} else {
|
||||
logger.info("Decoding app manifest")
|
||||
|
||||
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||
// because it does not support decoding to an OutputStream.
|
||||
AndroidManifestPullStreamDecoder(
|
||||
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||
resourcesDecoder.newXmlSerializer(),
|
||||
).decode(
|
||||
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
||||
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||
object : OutputStream() {
|
||||
override fun write(b: Int) { // Do nothing.
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||
// AndroidManifestPullStreamDecoder.decode() sets metadata.apkInfo.
|
||||
packageMetadata.let { metadata ->
|
||||
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
||||
versionInfo.let {
|
||||
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||
}
|
||||
|
||||
/*
|
||||
The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
|
||||
because ResourcesDecoder.decodeResources loads the main package
|
||||
and not AndroidManifestPullStreamDecoder.decode.
|
||||
See ARSCDecoder.readTableType for more info.
|
||||
|
||||
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
||||
*/
|
||||
metadata.apkInfo.sparseResources = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile resources in [PatcherConfig.apkFiles].
|
||||
*
|
||||
* @return The [PatcherResult.PatchedResources].
|
||||
*/
|
||||
@InternalApi
|
||||
override fun get(): PatcherResult.PatchedResources? {
|
||||
if (config.resourceMode == ResourceMode.NONE) return null
|
||||
|
||||
logger.info("Compiling modified resources")
|
||||
|
||||
val resources = config.patchedFiles.resolve("resources").also { it.mkdirs() }
|
||||
|
||||
val resourcesApkFile =
|
||||
if (config.resourceMode == ResourceMode.FULL) {
|
||||
resources.resolve("resources.apk").apply {
|
||||
// Compile the resources.apk file.
|
||||
AaptInvoker(
|
||||
config.resourceConfig,
|
||||
packageMetadata.apkInfo,
|
||||
).invoke(
|
||||
resources.resolve("resources.apk"),
|
||||
config.apkFiles.resolve("AndroidManifest.xml").also {
|
||||
ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
|
||||
},
|
||||
config.apkFiles.resolve("res"),
|
||||
null,
|
||||
null,
|
||||
packageMetadata.apkInfo.usesFramework.let { usesFramework ->
|
||||
usesFramework.ids.map { id ->
|
||||
Framework(config.resourceConfig).getFrameworkApk(id, usesFramework.tag)
|
||||
}.toTypedArray()
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val otherFiles =
|
||||
config.apkFiles.listFiles()!!.filter {
|
||||
// Excluded because present in resources.other.
|
||||
// TODO: We are reusing config.apkFiles as a temporarily directory for extracting resources.
|
||||
// This is not ideal as it could conflict with files such as the ones that we filter here.
|
||||
// The problem is that ResourcePatchContext#get returns a File relative to config.apkFiles,
|
||||
// and we need to extract files to that directory.
|
||||
// A solution would be to use config.apkFiles as the working directory for the patching process.
|
||||
// Once all patches have been executed, we can move the decoded resources to a new directory.
|
||||
// The filters wouldn't be needed anymore.
|
||||
// For now, we assume that the files we filter here are not needed for the patching process.
|
||||
it.name != "AndroidManifest.xml" &&
|
||||
it.name != "res" &&
|
||||
// Generated by Androlib.
|
||||
it.name != "build"
|
||||
}
|
||||
|
||||
val otherResourceFiles =
|
||||
if (otherFiles.isNotEmpty()) {
|
||||
// Move the other resources files.
|
||||
resources.resolve("other").also { it.mkdirs() }.apply {
|
||||
otherFiles.forEach { file ->
|
||||
Files.move(file.toPath(), resolve(file.name).toPath())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return PatcherResult.PatchedResources(
|
||||
resourcesApkFile,
|
||||
otherResourceFiles,
|
||||
packageMetadata.apkInfo.doNotCompress?.toSet() ?: emptySet(),
|
||||
deleteResources,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file from [PatcherConfig.apkFiles].
|
||||
*
|
||||
* @param path The path of the file.
|
||||
* @param copy Whether to copy the file from [PatcherConfig.apkFile] if it does not exist yet in [PatcherConfig.apkFiles].
|
||||
*/
|
||||
operator fun get(
|
||||
path: String,
|
||||
copy: Boolean = true,
|
||||
) = config.apkFiles.resolve(path).apply {
|
||||
if (copy && !exists()) {
|
||||
with(ExtFile(config.apkFile).directory) {
|
||||
if (containsFile(path) || containsDir(path)) {
|
||||
copyToDir(config.apkFiles, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a file for deletion when the APK is rebuilt.
|
||||
*
|
||||
* @param name The name of the file to delete.
|
||||
*/
|
||||
fun delete(name: String) = deleteResources.add(name)
|
||||
|
||||
/**
|
||||
* How to handle resources decoding and compiling.
|
||||
*/
|
||||
internal enum class ResourceMode {
|
||||
/**
|
||||
* Decode and compile all resources.
|
||||
*/
|
||||
FULL,
|
||||
|
||||
/**
|
||||
* Only extract resources from the APK.
|
||||
* The AndroidManifest.xml and resources inside /res are not decoded or compiled.
|
||||
*/
|
||||
RAW_ONLY,
|
||||
|
||||
/**
|
||||
* Do not decode or compile any resources.
|
||||
*/
|
||||
NONE,
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableClassDef
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableClassDef.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableField
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.filterAny
|
||||
@@ -13,19 +7,24 @@ import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.isPublic
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.toPublic
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.traverseClassHierarchy
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.logging.Logger
|
||||
import kotlin.reflect.KFunction2
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
/**
|
||||
* Experimental class to merge a [ClassDef] with another.
|
||||
* Note: This will not consider method implementations or if the class is missing a superclass or interfaces.
|
||||
*/
|
||||
internal object ClassMerger {
|
||||
private val logger = Logger.getLogger(ClassMerger::class.jvmName)
|
||||
private val logger = Logger.getLogger(ClassMerger::class.java.name)
|
||||
|
||||
/**
|
||||
* Merge a class with [otherClass].
|
||||
@@ -176,17 +175,18 @@ internal object ClassMerger {
|
||||
* @param callback function that is called for every class in the hierarchy
|
||||
*/
|
||||
fun BytecodePatchContext.traverseClassHierarchy(
|
||||
targetClass: MutableClassDef,
|
||||
callback: MutableClassDef.() -> Unit,
|
||||
targetClass: MutableClass,
|
||||
callback: MutableClass.() -> Unit,
|
||||
) {
|
||||
callback(targetClass)
|
||||
|
||||
classDefs[targetClass.superclass ?: return]?.let { classDef ->
|
||||
traverseClassHierarchy(classDefs.getOrReplaceMutable(classDef), callback)
|
||||
targetClass.superclass ?: return
|
||||
this.classBy { targetClass.superclass == it.type }?.mutableClass?.let {
|
||||
traverseClassHierarchy(it, callback)
|
||||
}
|
||||
}
|
||||
|
||||
fun ClassDef.asMutableClass() = this as? MutableClassDef ?: this.toMutable()
|
||||
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
|
||||
|
||||
/**
|
||||
* Check if the [AccessFlags.PUBLIC] flag is set.
|
||||
@@ -1,19 +1,14 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import collections.merge
|
||||
import com.google.common.base.Charsets
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.bufferedWriter
|
||||
import java.io.inputStream
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import kotlin.use
|
||||
|
||||
class Document internal constructor(
|
||||
inputStream: InputStream,
|
||||
@@ -34,7 +29,7 @@ class Document internal constructor(
|
||||
if (readerCount[it]!! > 1) {
|
||||
throw IllegalStateException(
|
||||
"Two or more instances are currently reading $it." +
|
||||
"To be able to close this instance, no other instances may be reading $it at the same time.",
|
||||
"To be able to close this instance, no other instances may be reading $it at the same time.",
|
||||
)
|
||||
} else {
|
||||
readerCount.remove(it)
|
||||
@@ -45,9 +40,9 @@ class Document internal constructor(
|
||||
if (isAndroid) {
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-16")
|
||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
|
||||
val writer = it.bufferedWriter(charset = Charsets.UTF_8)
|
||||
transformer.transform(DOMSource(this), StreamResult(writer))
|
||||
writer.close()
|
||||
it.bufferedWriter(charset = Charsets.UTF_8).use { writer ->
|
||||
transformer.transform(DOMSource(this), StreamResult(writer))
|
||||
}
|
||||
} else {
|
||||
transformer.transform(DOMSource(this), StreamResult(it))
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod
|
||||
import app.revanced.patcher.extensions.instructionsOrNull
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructionsOrNull
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.util.MethodNavigator.NavigateException
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
@@ -23,12 +24,12 @@ import kotlin.reflect.KProperty
|
||||
* @throws NavigateException If the method does not have an implementation.
|
||||
* @throws NavigateException If the instruction at the specified index is not a method reference.
|
||||
*/
|
||||
context(BytecodePatchContext)
|
||||
class MethodNavigator internal constructor(
|
||||
private var startMethod: MethodReference,
|
||||
) {
|
||||
private var lastNavigatedMethodReference = startMethod
|
||||
|
||||
context(_: BytecodePatchContext)
|
||||
private val lastNavigatedMethodInstructions
|
||||
get() = with(original()) {
|
||||
instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
|
||||
@@ -41,7 +42,6 @@ class MethodNavigator internal constructor(
|
||||
*
|
||||
* @return This [MethodNavigator].
|
||||
*/
|
||||
context(_: BytecodePatchContext)
|
||||
fun to(vararg index: Int): MethodNavigator {
|
||||
index.forEach {
|
||||
lastNavigatedMethodReference = lastNavigatedMethodInstructions.getMethodReferenceAt(it)
|
||||
@@ -56,7 +56,6 @@ class MethodNavigator internal constructor(
|
||||
* @param index The index of the method to navigate to.
|
||||
* @param predicate The predicate to match.
|
||||
*/
|
||||
context(_: BytecodePatchContext)
|
||||
fun to(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
|
||||
lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
|
||||
.filter(predicate).asIterable().getMethodReferenceAt(index)
|
||||
@@ -81,26 +80,29 @@ class MethodNavigator internal constructor(
|
||||
*
|
||||
* @return The last navigated method mutably.
|
||||
*/
|
||||
context(context: BytecodePatchContext)
|
||||
fun stop() = context.classDefs[lastNavigatedMethodReference.definingClass]!!
|
||||
.firstMethodBySignature as MutableMethod
|
||||
|
||||
fun stop() = classBy(matchesCurrentMethodReferenceDefiningClass)!!.mutableClass.firstMethodBySignature
|
||||
as MutableMethod
|
||||
|
||||
/**
|
||||
* Get the last navigated method mutably.
|
||||
*
|
||||
* @return The last navigated method mutably.
|
||||
*/
|
||||
operator fun getValue(context: BytecodePatchContext?, property: KProperty<*>) =
|
||||
context(requireNotNull(context)) { stop() }
|
||||
operator fun getValue(nothing: Nothing?, property: KProperty<*>) = stop()
|
||||
|
||||
/**
|
||||
* Get the last navigated method immutably.
|
||||
*
|
||||
* @return The last navigated method immutably.
|
||||
*/
|
||||
context(context: BytecodePatchContext)
|
||||
fun original(): Method = context.classDefs[lastNavigatedMethodReference.definingClass]!!.firstMethodBySignature
|
||||
fun original(): Method = classes.first(matchesCurrentMethodReferenceDefiningClass).firstMethodBySignature
|
||||
|
||||
/**
|
||||
* Predicate to match the class defining the current method reference.
|
||||
*/
|
||||
private val matchesCurrentMethodReferenceDefiningClass = { classDef: ClassDef ->
|
||||
classDef.type == lastNavigatedMethodReference.definingClass
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first [lastNavigatedMethodReference] in the class.
|
||||
29
src/main/kotlin/app/revanced/patcher/util/ProxyClassList.kt
Normal file
29
src/main/kotlin/app/revanced/patcher/util/ProxyClassList.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
/**
|
||||
* A list of classes and proxies.
|
||||
*
|
||||
* @param classes The classes to be backed by proxies.
|
||||
*/
|
||||
class ProxyClassList internal constructor(classes: MutableList<ClassDef>) : MutableList<ClassDef> by classes {
|
||||
internal val proxyPool = mutableListOf<ClassProxy>()
|
||||
|
||||
/**
|
||||
* Replace all classes with their mutated versions.
|
||||
*/
|
||||
internal fun replaceClasses() =
|
||||
proxyPool.removeIf { proxy ->
|
||||
// If the proxy is unused, return false to keep it in the proxies list.
|
||||
if (!proxy.resolved) return@removeIf false
|
||||
|
||||
// If it has been used, replace the original class with the mutable class.
|
||||
remove(proxy.immutableClass)
|
||||
add(proxy.mutableClass)
|
||||
|
||||
// Return true to remove the proxy from the proxies list.
|
||||
return@removeIf true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package app.revanced.patcher.util.proxy
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
|
||||
/**
|
||||
* A proxy class for a [ClassDef].
|
||||
*
|
||||
* A class proxy simply holds a reference to the original class
|
||||
* and allocates a mutable clone for the original class if needed.
|
||||
*
|
||||
* @param immutableClass The class to proxy.
|
||||
*/
|
||||
class ClassProxy internal constructor(
|
||||
val immutableClass: ClassDef,
|
||||
) {
|
||||
/**
|
||||
* Weather the proxy was actually used.
|
||||
*/
|
||||
internal var resolved = false
|
||||
|
||||
/**
|
||||
* The mutable clone of the original class.
|
||||
*
|
||||
* Note: This is only allocated if the proxy is actually used.
|
||||
*/
|
||||
val mutableClass by lazy {
|
||||
resolved = true
|
||||
if (immutableClass is MutableClass) {
|
||||
immutableClass
|
||||
} else {
|
||||
MutableClass(immutableClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
package com.android.tools.smali.dexlib2.mutable
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.base.BaseAnnotation
|
||||
import com.android.tools.smali.dexlib2.iface.Annotation
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableAnnotationElement.Companion.toMutable
|
||||
|
||||
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
|
||||
private val visibility = annotation.visibility
|
||||
private val type = annotation.type
|
||||
private val _elements by lazy { annotation.elements.map { element -> element.toMutable() }.toMutableSet() }
|
||||
|
||||
override fun getType() = type
|
||||
override fun getType(): String = type
|
||||
|
||||
override fun getElements() = _elements
|
||||
override fun getElements(): MutableSet<MutableAnnotationElement> = _elements
|
||||
|
||||
override fun getVisibility() = visibility
|
||||
override fun getVisibility(): Int = visibility
|
||||
|
||||
companion object {
|
||||
fun Annotation.toMutable() = MutableAnnotation(this)
|
||||
fun Annotation.toMutable(): MutableAnnotation = MutableAnnotation(this)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.android.tools.smali.dexlib2.mutable
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.base.BaseAnnotationElement
|
||||
import com.android.tools.smali.dexlib2.iface.AnnotationElement
|
||||
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.iface.value.EncodedValue
|
||||
|
||||
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
|
||||
private var name = annotationElement.name
|
||||
@@ -17,11 +18,11 @@ class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnot
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getName() = name
|
||||
override fun getName(): String = name
|
||||
|
||||
override fun getValue() = value
|
||||
override fun getValue(): EncodedValue = value
|
||||
|
||||
companion object {
|
||||
fun AnnotationElement.toMutable() = MutableAnnotationElement(this)
|
||||
fun AnnotationElement.toMutable(): MutableAnnotationElement = MutableAnnotationElement(this)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.android.tools.smali.dexlib2.mutable
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.util.FieldUtil
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
class MutableClassDef(classDef: ClassDef) : BaseTypeReference(), ClassDef {
|
||||
class MutableClass(classDef: ClassDef) :
|
||||
BaseTypeReference(),
|
||||
ClassDef {
|
||||
// Class
|
||||
private var type = classDef.type
|
||||
private var sourceFile = classDef.sourceFile
|
||||
@@ -22,8 +24,8 @@ class MutableClassDef(classDef: ClassDef) : BaseTypeReference(), ClassDef {
|
||||
|
||||
// Methods
|
||||
private val _methods by lazy { classDef.methods.map { method -> method.toMutable() }.toMutableSet() }
|
||||
private val _directMethods by lazy { methods.filter { method -> MethodUtil.isDirect(method) }.toMutableSet() }
|
||||
private val _virtualMethods by lazy { methods.filter { method -> !MethodUtil.isDirect(method) }.toMutableSet() }
|
||||
private val _directMethods by lazy { _methods.filter { method -> MethodUtil.isDirect(method) }.toMutableSet() }
|
||||
private val _virtualMethods by lazy { _methods.filter { method -> !MethodUtil.isDirect(method) }.toMutableSet() }
|
||||
|
||||
// Fields
|
||||
private val _fields by lazy { classDef.fields.map { field -> field.toMutable() }.toMutableSet() }
|
||||
@@ -46,31 +48,31 @@ class MutableClassDef(classDef: ClassDef) : BaseTypeReference(), ClassDef {
|
||||
this.superclass = superclass
|
||||
}
|
||||
|
||||
override fun getType() = type
|
||||
override fun getType(): String = type
|
||||
|
||||
override fun getAccessFlags() = accessFlags
|
||||
override fun getAccessFlags(): Int = accessFlags
|
||||
|
||||
override fun getSourceFile() = sourceFile
|
||||
override fun getSourceFile(): String? = sourceFile
|
||||
|
||||
override fun getSuperclass() = superclass
|
||||
override fun getSuperclass(): String? = superclass
|
||||
|
||||
override fun getInterfaces() = _interfaces
|
||||
override fun getInterfaces(): MutableList<String> = _interfaces
|
||||
|
||||
override fun getAnnotations() = _annotations
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = _annotations
|
||||
|
||||
override fun getStaticFields() = _staticFields
|
||||
override fun getStaticFields(): MutableSet<MutableField> = _staticFields
|
||||
|
||||
override fun getInstanceFields() = _instanceFields
|
||||
override fun getInstanceFields(): MutableSet<MutableField> = _instanceFields
|
||||
|
||||
override fun getFields() = _fields
|
||||
override fun getFields(): MutableSet<MutableField> = _fields
|
||||
|
||||
override fun getDirectMethods() = _directMethods
|
||||
override fun getDirectMethods(): MutableSet<MutableMethod> = _directMethods
|
||||
|
||||
override fun getVirtualMethods() = _virtualMethods
|
||||
override fun getVirtualMethods(): MutableSet<MutableMethod> = _virtualMethods
|
||||
|
||||
override fun getMethods() = _methods
|
||||
override fun getMethods(): MutableSet<MutableMethod> = _methods
|
||||
|
||||
companion object {
|
||||
fun ClassDef.toMutable(): MutableClassDef = MutableClassDef(this)
|
||||
fun ClassDef.toMutable(): MutableClass = MutableClass(this)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.android.tools.smali.dexlib2.mutable
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.HiddenApiRestriction
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.Field
|
||||
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
|
||||
|
||||
class MutableField(field: Field) : BaseFieldReference(), Field {
|
||||
class MutableField(field: Field) :
|
||||
BaseFieldReference(),
|
||||
Field {
|
||||
private var definingClass = field.definingClass
|
||||
private var name = field.name
|
||||
private var type = field.type
|
||||
@@ -36,21 +39,21 @@ class MutableField(field: Field) : BaseFieldReference(), Field {
|
||||
this.initialValue = initialValue
|
||||
}
|
||||
|
||||
override fun getDefiningClass() = this.definingClass
|
||||
override fun getDefiningClass(): String = this.definingClass
|
||||
|
||||
override fun getName() = this.name
|
||||
override fun getName(): String = this.name
|
||||
|
||||
override fun getType() = this.type
|
||||
override fun getType(): String = this.type
|
||||
|
||||
override fun getAnnotations() = this._annotations
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = this._annotations
|
||||
|
||||
override fun getAccessFlags() = this.accessFlags
|
||||
override fun getAccessFlags(): Int = this.accessFlags
|
||||
|
||||
override fun getHiddenApiRestrictions() = this._hiddenApiRestrictions
|
||||
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> = this._hiddenApiRestrictions
|
||||
|
||||
override fun getInitialValue() = this.initialValue
|
||||
override fun getInitialValue(): MutableEncodedValue? = this.initialValue
|
||||
|
||||
companion object {
|
||||
fun Field.toMutable() = MutableField(this)
|
||||
fun Field.toMutable(): MutableField = MutableField(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethodParameter.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.HiddenApiRestriction
|
||||
import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
|
||||
class MutableMethod(method: Method) :
|
||||
BaseMethodReference(),
|
||||
Method {
|
||||
private var definingClass = method.definingClass
|
||||
private var name = method.name
|
||||
private var accessFlags = method.accessFlags
|
||||
private var returnType = method.returnType
|
||||
|
||||
// Create own mutable MethodImplementation (due to not being able to change members like register count)
|
||||
private val _implementation by lazy { method.implementation?.let { MutableMethodImplementation(it) } }
|
||||
private val _annotations by lazy { method.annotations.map { annotation -> annotation.toMutable() }.toMutableSet() }
|
||||
private val _parameters by lazy { method.parameters.map { parameter -> parameter.toMutable() }.toMutableList() }
|
||||
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
|
||||
private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
|
||||
|
||||
fun setDefiningClass(definingClass: String) {
|
||||
this.definingClass = definingClass
|
||||
}
|
||||
|
||||
fun setName(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun setAccessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
fun setReturnType(returnType: String) {
|
||||
this.returnType = returnType
|
||||
}
|
||||
|
||||
override fun getDefiningClass(): String = definingClass
|
||||
|
||||
override fun getName(): String = name
|
||||
|
||||
override fun getParameterTypes(): MutableList<CharSequence> = _parameterTypes
|
||||
|
||||
override fun getReturnType(): String = returnType
|
||||
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = _annotations
|
||||
|
||||
override fun getAccessFlags(): Int = accessFlags
|
||||
|
||||
override fun getHiddenApiRestrictions(): MutableSet<HiddenApiRestriction> = _hiddenApiRestrictions
|
||||
|
||||
override fun getParameters(): MutableList<MutableMethodParameter> = _parameters
|
||||
|
||||
override fun getImplementation(): MutableMethodImplementation? = _implementation
|
||||
|
||||
companion object {
|
||||
fun Method.toMutable(): MutableMethod = MutableMethod(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.base.BaseMethodParameter
|
||||
import com.android.tools.smali.dexlib2.iface.MethodParameter
|
||||
|
||||
// TODO: finish overriding all members if necessary
|
||||
class MutableMethodParameter(parameter: MethodParameter) :
|
||||
BaseMethodParameter(),
|
||||
MethodParameter {
|
||||
private var type = parameter.type
|
||||
private var name = parameter.name
|
||||
private var signature = parameter.signature
|
||||
private val _annotations by lazy {
|
||||
parameter.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
override fun getType(): String = type
|
||||
|
||||
override fun getName(): String? = name
|
||||
|
||||
override fun getSignature(): String? = signature
|
||||
|
||||
override fun getAnnotations(): MutableSet<MutableAnnotation> = _annotations
|
||||
|
||||
companion object {
|
||||
fun MethodParameter.toMutable(): MutableMethodParameter = MutableMethodParameter(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.AnnotationElement
|
||||
import com.android.tools.smali.dexlib2.iface.value.AnnotationEncodedValue
|
||||
|
||||
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) :
|
||||
BaseAnnotationEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var type = annotationEncodedValue.type
|
||||
|
||||
private val _elements by lazy {
|
||||
annotationEncodedValue.elements.map { annotationElement -> annotationElement.toMutable() }.toMutableSet()
|
||||
}
|
||||
|
||||
override fun getType(): String = this.type
|
||||
|
||||
fun setType(type: String) {
|
||||
this.type = type
|
||||
}
|
||||
|
||||
override fun getElements(): MutableSet<out AnnotationElement> = _elements
|
||||
|
||||
companion object {
|
||||
fun AnnotationEncodedValue.toMutable(): MutableAnnotationEncodedValue = MutableAnnotationEncodedValue(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseArrayEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ArrayEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.EncodedValue
|
||||
|
||||
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) :
|
||||
BaseArrayEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private val _value by lazy {
|
||||
arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
|
||||
}
|
||||
|
||||
override fun getValue(): MutableList<out EncodedValue> = _value
|
||||
|
||||
companion object {
|
||||
fun ArrayEncodedValue.toMutable(): MutableArrayEncodedValue = MutableArrayEncodedValue(this)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseBooleanEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.BooleanEncodedValue
|
||||
|
||||
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) :
|
||||
BaseBooleanEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = booleanEncodedValue.value
|
||||
|
||||
override fun getValue(): Boolean = this.value
|
||||
|
||||
fun setValue(value: Boolean) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Boolean = this.value
|
||||
|
||||
companion object {
|
||||
fun BooleanEncodedValue.toMutable(): MutableBooleanEncodedValue = MutableBooleanEncodedValue(this)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||
|
||||
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
|
||||
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) :
|
||||
BaseByteEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = byteEncodedValue.value
|
||||
|
||||
override fun getValue(): Byte = this.value
|
||||
|
||||
fun setValue(value: Byte) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Byte = this.value
|
||||
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue
|
||||
|
||||
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
|
||||
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) :
|
||||
BaseCharEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = charEncodedValue.value
|
||||
|
||||
override fun getValue(): Char = this.value
|
||||
|
||||
fun setValue(value: Char) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Char = this.value
|
||||
|
||||
companion object {
|
||||
fun CharEncodedValue.toMutable(): MutableCharEncodedValue = MutableCharEncodedValue(this)
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseDoubleEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.DoubleEncodedValue
|
||||
|
||||
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) :
|
||||
BaseDoubleEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = doubleEncodedValue.value
|
||||
|
||||
override fun getValue(): Double = this.value
|
||||
|
||||
fun setValue(value: Double) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Double = this.value
|
||||
|
||||
companion object {
|
||||
fun DoubleEncodedValue.toMutable(): MutableDoubleEncodedValue = MutableDoubleEncodedValue(this)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.ValueType
|
||||
import com.android.tools.smali.dexlib2.iface.value.*
|
||||
|
||||
interface MutableEncodedValue : EncodedValue {
|
||||
companion object {
|
||||
@@ -1,17 +1,20 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseEnumEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.EnumEncodedValue
|
||||
|
||||
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
|
||||
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) :
|
||||
BaseEnumEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = enumEncodedValue.value
|
||||
|
||||
override fun getValue(): FieldReference = this.value
|
||||
|
||||
fun setValue(value: FieldReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): FieldReference = this.value
|
||||
|
||||
companion object {
|
||||
fun EnumEncodedValue.toMutable(): MutableEnumEncodedValue = MutableEnumEncodedValue(this)
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.ValueType
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseFieldEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.FieldEncodedValue
|
||||
|
||||
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
|
||||
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) :
|
||||
BaseFieldEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = fieldEncodedValue.value
|
||||
|
||||
fun setValue(value: FieldReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValueType(): Int = ValueType.FIELD
|
||||
|
||||
override fun getValue(): FieldReference = this.value
|
||||
|
||||
fun setValue(value: FieldReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun FieldEncodedValue.toMutable(): MutableFieldEncodedValue = MutableFieldEncodedValue(this)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue
|
||||
|
||||
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
|
||||
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) :
|
||||
BaseFloatEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = floatEncodedValue.value
|
||||
|
||||
override fun getValue(): Float = this.value
|
||||
|
||||
fun setValue(value: Float) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Float = this.value
|
||||
|
||||
companion object {
|
||||
fun FloatEncodedValue.toMutable(): MutableFloatEncodedValue = MutableFloatEncodedValue(this)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue
|
||||
|
||||
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
|
||||
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) :
|
||||
BaseIntEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = intEncodedValue.value
|
||||
|
||||
override fun getValue(): Int = this.value
|
||||
|
||||
fun setValue(value: Int) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Int = this.value
|
||||
|
||||
companion object {
|
||||
fun IntEncodedValue.toMutable(): MutableIntEncodedValue = MutableIntEncodedValue(this)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue
|
||||
|
||||
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
|
||||
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) :
|
||||
BaseLongEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = longEncodedValue.value
|
||||
|
||||
override fun getValue(): Long = this.value
|
||||
|
||||
fun setValue(value: Long) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Long = this.value
|
||||
|
||||
companion object {
|
||||
fun LongEncodedValue.toMutable(): MutableLongEncodedValue = MutableLongEncodedValue(this)
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseMethodEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.MethodEncodedValue
|
||||
|
||||
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) :
|
||||
BaseMethodEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = methodEncodedValue.value
|
||||
|
||||
override fun getValue(): MethodReference = this.value
|
||||
|
||||
fun setValue(value: MethodReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): MethodReference = this.value
|
||||
|
||||
companion object {
|
||||
fun MethodEncodedValue.toMutable(): MutableMethodEncodedValue = MutableMethodEncodedValue(this)
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseMethodHandleEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodHandleReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.MethodHandleEncodedValue
|
||||
|
||||
class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) :
|
||||
BaseMethodHandleEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = methodHandleEncodedValue.value
|
||||
|
||||
override fun getValue(): MethodHandleReference = this.value
|
||||
|
||||
fun setValue(value: MethodHandleReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): MethodHandleReference = this.value
|
||||
|
||||
companion object {
|
||||
fun MethodHandleEncodedValue.toMutable(): MutableMethodHandleEncodedValue = MutableMethodHandleEncodedValue(this)
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseMethodTypeEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodProtoReference
|
||||
import com.android.tools.smali.dexlib2.iface.value.MethodTypeEncodedValue
|
||||
|
||||
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(), MutableEncodedValue {
|
||||
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) :
|
||||
BaseMethodTypeEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = methodTypeEncodedValue.value
|
||||
|
||||
override fun getValue(): MethodProtoReference = this.value
|
||||
|
||||
fun setValue(value: MethodProtoReference) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): MethodProtoReference = this.value
|
||||
|
||||
companion object {
|
||||
fun MethodTypeEncodedValue.toMutable(): MutableMethodTypeEncodedValue = MutableMethodTypeEncodedValue(this)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||
|
||||
class MutableNullEncodedValue :
|
||||
BaseNullEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue
|
||||
|
||||
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) :
|
||||
BaseShortEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = shortEncodedValue.value
|
||||
|
||||
override fun getValue(): Short = this.value
|
||||
|
||||
fun setValue(value: Short) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): Short = this.value
|
||||
|
||||
companion object {
|
||||
fun ShortEncodedValue.toMutable(): MutableShortEncodedValue = MutableShortEncodedValue(this)
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseStringEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) :
|
||||
BaseStringEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = stringEncodedValue.value
|
||||
|
||||
override fun getValue(): String = this.value
|
||||
|
||||
fun setValue(value: String) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue(): String = this.value
|
||||
|
||||
companion object {
|
||||
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.android.tools.smali.dexlib2.iface.value
|
||||
package app.revanced.patcher.util.proxy.mutableTypes.encodedValue
|
||||
|
||||
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue
|
||||
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue
|
||||
|
||||
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
|
||||
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) :
|
||||
BaseTypeEncodedValue(),
|
||||
MutableEncodedValue {
|
||||
private var value = typeEncodedValue.value
|
||||
|
||||
override fun getValue(): String = this.value
|
||||
|
||||
fun setValue(value: String) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
override fun getValue() = this.value
|
||||
|
||||
companion object {
|
||||
fun TypeEncodedValue.toMutable(): MutableTypeEncodedValue = MutableTypeEncodedValue(this)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
|
||||
/**
|
||||
* A class that represents a label for an instruction.
|
||||
* @param name The label name.
|
||||
* @param instruction The instruction that this label is for.
|
||||
*/
|
||||
data class ExternalLabel(internal val name: String, internal val instruction: Instruction)
|
||||
@@ -0,0 +1,95 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcodes
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.writer.builder.DexBuilder
|
||||
import com.android.tools.smali.smali.LexerErrorInterface
|
||||
import com.android.tools.smali.smali.smaliFlexLexer
|
||||
import com.android.tools.smali.smali.smaliParser
|
||||
import com.android.tools.smali.smali.smaliTreeWalker
|
||||
import org.antlr.runtime.CommonTokenStream
|
||||
import org.antlr.runtime.TokenSource
|
||||
import org.antlr.runtime.tree.CommonTreeNodeStream
|
||||
import java.io.InputStreamReader
|
||||
import java.util.*
|
||||
|
||||
private const val METHOD_TEMPLATE = """
|
||||
.class LInlineCompiler;
|
||||
.super Ljava/lang/Object;
|
||||
.method %s dummyMethod(%s)V
|
||||
.registers %d
|
||||
%s
|
||||
.end method
|
||||
"""
|
||||
|
||||
class InlineSmaliCompiler {
|
||||
companion object {
|
||||
/**
|
||||
* Compiles a string of Smali code to a list of instructions.
|
||||
* Special registers (such as p0, p1) will only work correctly
|
||||
* if the parameters and registers of the method are passed.
|
||||
*/
|
||||
fun compile(
|
||||
instructions: String,
|
||||
parameters: String,
|
||||
registers: Int,
|
||||
forStaticMethod: Boolean,
|
||||
): List<BuilderInstruction> {
|
||||
val input =
|
||||
METHOD_TEMPLATE.format(
|
||||
Locale.ENGLISH,
|
||||
if (forStaticMethod) {
|
||||
"static"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
parameters,
|
||||
registers,
|
||||
instructions,
|
||||
)
|
||||
val reader = InputStreamReader(input.byteInputStream(), Charsets.UTF_8)
|
||||
val lexer: LexerErrorInterface = smaliFlexLexer(reader, 15)
|
||||
val tokens = CommonTokenStream(lexer as TokenSource)
|
||||
val parser = smaliParser(tokens)
|
||||
val result = parser.smali_file()
|
||||
if (parser.numberOfSyntaxErrors > 0 || lexer.numberOfSyntaxErrors > 0) {
|
||||
throw IllegalStateException(
|
||||
"Encountered ${parser.numberOfSyntaxErrors} parser syntax errors and ${lexer.numberOfSyntaxErrors} lexer syntax errors!",
|
||||
)
|
||||
}
|
||||
val treeStream = CommonTreeNodeStream(result.tree)
|
||||
treeStream.tokenStream = tokens
|
||||
val dexGen = smaliTreeWalker(treeStream)
|
||||
dexGen.setDexBuilder(DexBuilder(Opcodes.getDefault()))
|
||||
val classDef = dexGen.smali_file()
|
||||
return classDef.methods.first().instructions.map { it as BuilderInstruction }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile lines of Smali code to a list of instructions.
|
||||
*
|
||||
* Note: Adding compiled instructions to an existing method with
|
||||
* offset instructions WITHOUT specifying a parent method will not work.
|
||||
* @param method The method to compile the instructions against.
|
||||
* @returns A list of instructions.
|
||||
*/
|
||||
fun String.toInstructions(method: MutableMethod? = null): List<BuilderInstruction> {
|
||||
return InlineSmaliCompiler.compile(
|
||||
this,
|
||||
method?.parameters?.joinToString("") { it } ?: "",
|
||||
method?.implementation?.registerCount ?: 1,
|
||||
method?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a line of Smali code to an instruction.
|
||||
* @param templateMethod The method to compile the instructions against.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun String.toInstruction(templateMethod: MutableMethod? = null) = this.toInstructions(templateMethod).first()
|
||||
@@ -0,0 +1 @@
|
||||
version=${projectVersion}
|
||||
236
src/test/kotlin/app/revanced/patcher/PatcherTest.kt
Normal file
236
src/test/kotlin/app/revanced/patcher/PatcherTest.kt
Normal file
@@ -0,0 +1,236 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.patch.BytecodePatchContext.LookupMaps
|
||||
import app.revanced.patcher.util.ProxyClassList
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableClassDef
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.assertAll
|
||||
import java.util.logging.Logger
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal object PatcherTest {
|
||||
private lateinit var patcher: Patcher
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
patcher = mockk<Patcher> {
|
||||
// Can't mock private fields, until https://github.com/mockk/mockk/issues/1244 is resolved.
|
||||
setPrivateField(
|
||||
"config",
|
||||
mockk<PatcherConfig> {
|
||||
every { resourceMode } returns ResourcePatchContext.ResourceMode.NONE
|
||||
},
|
||||
)
|
||||
setPrivateField(
|
||||
"logger",
|
||||
Logger.getAnonymousLogger(),
|
||||
)
|
||||
|
||||
every { context.bytecodeContext.classes } returns mockk(relaxed = true)
|
||||
every { this@mockk() } answers { callOriginal() }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `executes patches in correct order`() {
|
||||
val executed = mutableListOf<String>()
|
||||
|
||||
val patches = setOf(
|
||||
bytecodePatch { execute { executed += "1" } },
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
execute { executed += "2" }
|
||||
finalize { executed += "-2" }
|
||||
},
|
||||
bytecodePatch { execute { executed += "3" } },
|
||||
)
|
||||
|
||||
execute { executed += "4" }
|
||||
finalize { executed += "-1" }
|
||||
},
|
||||
)
|
||||
|
||||
assert(executed.isEmpty())
|
||||
|
||||
patches()
|
||||
|
||||
assertEquals(
|
||||
listOf("1", "2", "3", "4", "-1", "-2"),
|
||||
executed,
|
||||
"Expected patches to be executed in correct order.",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handles execution of patches correctly when exceptions occur`() {
|
||||
val executed = mutableListOf<String>()
|
||||
|
||||
infix fun Patch<*>.produces(equals: List<String>) {
|
||||
val patches = setOf(this)
|
||||
|
||||
patches()
|
||||
|
||||
assertEquals(equals, executed, "Expected patches to be executed in correct order.")
|
||||
|
||||
executed.clear()
|
||||
}
|
||||
|
||||
// No patches execute successfully,
|
||||
// because the dependency patch throws an exception inside the execute block.
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
execute { throw PatchException("1") }
|
||||
finalize { executed += "-2" }
|
||||
},
|
||||
)
|
||||
|
||||
execute { executed += "2" }
|
||||
finalize { executed += "-1" }
|
||||
} produces emptyList()
|
||||
|
||||
// The dependency patch is executed successfully,
|
||||
// because only the dependant patch throws an exception inside the finalize block.
|
||||
// Patches that depend on a failed patch should not be executed,
|
||||
// but patches that are depended on by a failed patch should be executed.
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
execute { executed += "1" }
|
||||
finalize { executed += "-2" }
|
||||
},
|
||||
)
|
||||
|
||||
execute { throw PatchException("2") }
|
||||
finalize { executed += "-1" }
|
||||
} produces listOf("1", "-2")
|
||||
|
||||
// Because the finalize block of the dependency patch is executed after the finalize block of the dependant patch,
|
||||
// the dependant patch executes successfully, but the dependency patch raises an exception in the finalize block.
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
execute { executed += "1" }
|
||||
finalize { throw PatchException("-2") }
|
||||
},
|
||||
)
|
||||
|
||||
execute { executed += "2" }
|
||||
finalize { executed += "-1" }
|
||||
} produces listOf("1", "2", "-1")
|
||||
|
||||
// The dependency patch is executed successfully,
|
||||
// because the dependant patch raises an exception in the finalize block.
|
||||
// Patches that depend on a failed patch should not be executed,
|
||||
// but patches that are depended on by a failed patch should be executed.
|
||||
bytecodePatch {
|
||||
dependsOn(
|
||||
bytecodePatch {
|
||||
execute { executed += "1" }
|
||||
finalize { executed += "-2" }
|
||||
},
|
||||
)
|
||||
|
||||
execute { executed += "2" }
|
||||
finalize { throw PatchException("-1") }
|
||||
} produces listOf("1", "2", "-2")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `throws if unmatched fingerprint match is delegated`() {
|
||||
val patch = bytecodePatch {
|
||||
execute {
|
||||
// Fingerprint can never match.
|
||||
val fingerprint = fingerprint { }
|
||||
|
||||
// Throws, because the fingerprint can't be matched.
|
||||
fingerprint.patternMatch
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
patch().exception != null,
|
||||
"Expected an exception because the fingerprint can't match.",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `matches fingerprint`() {
|
||||
every { patcher.context.bytecodeContext.classes } returns ProxyClassList(
|
||||
mutableListOf(
|
||||
ImmutableClassDef(
|
||||
"class",
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
listOf(
|
||||
ImmutableMethod(
|
||||
"class",
|
||||
"method",
|
||||
emptyList(),
|
||||
"V",
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val fingerprint = fingerprint { returns("V") }
|
||||
val fingerprint2 = fingerprint { returns("V") }
|
||||
val fingerprint3 = fingerprint { returns("V") }
|
||||
|
||||
val patches = setOf(
|
||||
bytecodePatch {
|
||||
execute {
|
||||
fingerprint.match(classes.first().methods.first())
|
||||
fingerprint2.match(classes.first())
|
||||
fingerprint3.originalClassDef
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
patches()
|
||||
|
||||
with(patcher.context.bytecodeContext) {
|
||||
assertAll(
|
||||
"Expected fingerprints to match.",
|
||||
{ assertNotNull(fingerprint.originalClassDefOrNull) },
|
||||
{ assertNotNull(fingerprint2.originalClassDefOrNull) },
|
||||
{ assertNotNull(fingerprint3.originalClassDefOrNull) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun Set<Patch<*>>.invoke(): List<PatchResult> {
|
||||
every { patcher.context.executablePatches } returns toMutableSet()
|
||||
every { patcher.context.bytecodeContext.lookupMaps } returns LookupMaps(patcher.context.bytecodeContext.classes)
|
||||
every { with(patcher.context.bytecodeContext) { mergeExtension(any<BytecodePatch>()) } } just runs
|
||||
|
||||
return runBlocking { patcher().toList() }
|
||||
}
|
||||
|
||||
private operator fun Patch<*>.invoke() = setOf(this)().first()
|
||||
|
||||
private fun Any.setPrivateField(field: String, value: Any) {
|
||||
this::class.java.getDeclaredField(field).apply {
|
||||
this.isAccessible = true
|
||||
set(this@setPrivateField, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
|
||||
@@ -7,30 +18,28 @@ import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21s
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod
|
||||
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class MethodExtensionsTest {
|
||||
private val testInstructions = (0..9).map { i -> TestInstruction(i) }
|
||||
private var method = ImmutableMethod(
|
||||
"TestClass;",
|
||||
"testMethod",
|
||||
null,
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(16)
|
||||
).toMutable()
|
||||
private object InstructionExtensionsTest {
|
||||
private lateinit var testMethod: MutableMethod
|
||||
private lateinit var testMethodImplementation: MutableMethodImplementation
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
method.instructions.clear()
|
||||
method.addInstructions(testInstructions)
|
||||
}
|
||||
fun createTestMethod() =
|
||||
ImmutableMethod(
|
||||
"TestClass;",
|
||||
"testMethod",
|
||||
null,
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(16).also { testMethodImplementation = it }.apply {
|
||||
repeat(10) { i -> this.addInstruction(TestInstruction(i)) }
|
||||
},
|
||||
).let { testMethod = it.toMutable() }
|
||||
|
||||
@Test
|
||||
fun addInstructionsToImplementationIndexed() =
|
||||
@@ -218,11 +227,11 @@ internal class MethodExtensionsTest {
|
||||
// region Helper methods
|
||||
|
||||
private fun applyToImplementation(block: MutableMethodImplementation.() -> Unit) {
|
||||
method.implementation!!.apply(block)
|
||||
testMethodImplementation.apply(block)
|
||||
}
|
||||
|
||||
private fun applyToMethod(block: MutableMethod.() -> Unit) {
|
||||
method.apply(block)
|
||||
testMethod.apply(block)
|
||||
}
|
||||
|
||||
private fun MutableMethodImplementation.assertRegisterIs(
|
||||
@@ -0,0 +1,90 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
// region Test patches.
|
||||
|
||||
// Not loaded, because it's unnamed.
|
||||
val publicUnnamedPatch = bytecodePatch {
|
||||
}
|
||||
|
||||
// Loaded, because it's named.
|
||||
val publicPatch = bytecodePatch("Public") {
|
||||
}
|
||||
|
||||
// Not loaded, because it's private.
|
||||
private val privateUnnamedPatch = bytecodePatch {
|
||||
}
|
||||
|
||||
// Not loaded, because it's private.
|
||||
private val privatePatch = bytecodePatch("Private") {
|
||||
}
|
||||
|
||||
// Not loaded, because it's unnamed.
|
||||
fun publicUnnamedPatchFunction() = publicUnnamedPatch
|
||||
|
||||
// Loaded, because it's named.
|
||||
fun publicNamedPatchFunction() = bytecodePatch("Public") { }
|
||||
|
||||
// Not loaded, because it's parameterized.
|
||||
fun parameterizedFunction(@Suppress("UNUSED_PARAMETER") param: Any) = publicNamedPatchFunction()
|
||||
|
||||
// Not loaded, because it's private.
|
||||
private fun privateUnnamedPatchFunction() = privateUnnamedPatch
|
||||
|
||||
// Not loaded, because it's private.
|
||||
private fun privateNamedPatchFunction() = privatePatch
|
||||
|
||||
// endregion
|
||||
|
||||
internal object PatchLoaderTest {
|
||||
private const val LOAD_PATCHES_FUNCTION_NAME = "loadPatches"
|
||||
private val TEST_PATCHES_CLASS = ::publicPatch.javaField!!.declaringClass.name
|
||||
private val TEST_PATCHES_CLASS_LOADER = ::publicPatch.javaClass.classLoader
|
||||
|
||||
@Test
|
||||
fun `loads patches correctly`() {
|
||||
// Get instance of private PatchLoader.Companion class.
|
||||
val patchLoaderCompanionObject = getPrivateFieldByType(
|
||||
PatchLoader::class.java,
|
||||
PatchLoader::class.companionObject!!.javaObjectType,
|
||||
)
|
||||
|
||||
// Get private PatchLoader.Companion.loadPatches function from PatchLoader.Companion.
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val loadPatchesFunction = getPrivateFunctionByName(
|
||||
patchLoaderCompanionObject,
|
||||
LOAD_PATCHES_FUNCTION_NAME,
|
||||
) as KFunction<Map<File, Set<Patch<*>>>>
|
||||
|
||||
// Call private PatchLoader.Companion.loadPatches function.
|
||||
val patches = loadPatchesFunction.call(
|
||||
patchLoaderCompanionObject,
|
||||
TEST_PATCHES_CLASS_LOADER,
|
||||
mapOf(File("patchesFile") to setOf(TEST_PATCHES_CLASS)),
|
||||
).values.first()
|
||||
|
||||
assertEquals(
|
||||
2,
|
||||
patches.size,
|
||||
"Expected 2 patches to be loaded, " +
|
||||
"because there's only two named patches declared as public static fields " +
|
||||
"or returned by public static and non-parametrized methods.",
|
||||
)
|
||||
}
|
||||
|
||||
private fun getPrivateFieldByType(cls: Class<*>, fieldType: Class<*>) =
|
||||
cls.declaredFields.first { it.type == fieldType }.apply { isAccessible = true }.get(null)
|
||||
|
||||
private fun getPrivateFunctionByName(obj: Any, @Suppress("SameParameterValue") methodName: String) =
|
||||
obj::class.declaredFunctions.first { it.name == methodName }.apply { isAccessible = true }
|
||||
}
|
||||
49
src/test/kotlin/app/revanced/patcher/patch/PatchTest.kt
Normal file
49
src/test/kotlin/app/revanced/patcher/patch/PatchTest.kt
Normal file
@@ -0,0 +1,49 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal object PatchTest {
|
||||
@Test
|
||||
fun `can create patch with name`() {
|
||||
val patch = bytecodePatch(name = "Test") {}
|
||||
|
||||
assertEquals("Test", patch.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can create patch with compatible packages`() {
|
||||
val patch = bytecodePatch(name = "Test") {
|
||||
compatibleWith(
|
||||
"compatible.package"("1.0.0"),
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(1, patch.compatiblePackages!!.size)
|
||||
assertEquals("compatible.package", patch.compatiblePackages!!.first().first)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can create patch with dependencies`() {
|
||||
val patch = bytecodePatch(name = "Test") {
|
||||
dependsOn(resourcePatch {})
|
||||
}
|
||||
|
||||
assertEquals(1, patch.dependencies.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `can create patch with options`() {
|
||||
val patch = bytecodePatch(name = "Test") {
|
||||
val print by stringOption("print")
|
||||
val custom = option<String>("custom")()
|
||||
|
||||
execute {
|
||||
println(print)
|
||||
println(custom.value)
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(2, patch.options.size)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
package app.revanced.patcher.patch
|
||||
package app.revanced.patcher.patch.options
|
||||
|
||||
import app.revanced.patcher.patch.*
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
internal object OptionsTest {
|
||||
private val externalOption = stringOption("external", "default")
|
||||
@@ -143,4 +141,4 @@ internal object OptionsTest {
|
||||
}
|
||||
|
||||
private fun options(block: Options.() -> Unit) = optionsTestPatch.options.let(block)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.newLabel
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21t
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
|
||||
import java.util.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal object InlineSmaliCompilerTest {
|
||||
@Test
|
||||
fun `outputs valid instruction`() {
|
||||
val want = BuilderInstruction21c(Opcode.CONST_STRING, 0, ImmutableStringReference("Test")) as BuilderInstruction
|
||||
val have = "const-string v0, \"Test\"".toInstruction()
|
||||
|
||||
assertInstructionsEqual(want, have)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `supports branching with own branches`() {
|
||||
val method = createMethod()
|
||||
val instructionCount = 8
|
||||
val instructionIndex = instructionCount - 2
|
||||
val targetIndex = instructionIndex - 1
|
||||
|
||||
method.addInstructions(
|
||||
arrayOfNulls<String>(instructionCount).also {
|
||||
Arrays.fill(it, "const/4 v0, 0x0")
|
||||
}.joinToString("\n"),
|
||||
)
|
||||
method.addInstructionsWithLabels(
|
||||
targetIndex,
|
||||
"""
|
||||
:test
|
||||
const/4 v0, 0x1
|
||||
if-eqz v0, :test
|
||||
""",
|
||||
)
|
||||
|
||||
val instruction = method.getInstruction<BuilderInstruction21t>(instructionIndex)
|
||||
|
||||
assertEquals(targetIndex, instruction.target.location.index)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `supports branching to outside branches`() {
|
||||
val method = createMethod()
|
||||
val instructionIndex = 3
|
||||
val labelIndex = 1
|
||||
|
||||
method.addInstructions(
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
const/4 v0, 0x0
|
||||
""",
|
||||
)
|
||||
|
||||
assertEquals(labelIndex, method.newLabel(labelIndex).location.index)
|
||||
|
||||
method.addInstructionsWithLabels(
|
||||
method.implementation!!.instructions.size,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
if-eqz v0, :test
|
||||
return-void
|
||||
""",
|
||||
ExternalLabel("test", method.getInstruction(1)),
|
||||
)
|
||||
|
||||
val instruction = method.getInstruction<BuilderInstruction21t>(instructionIndex)
|
||||
assertTrue(instruction.target.isPlaced, "Label was not placed")
|
||||
assertEquals(labelIndex, instruction.target.location.index)
|
||||
}
|
||||
|
||||
private fun createMethod(
|
||||
name: String = "dummy",
|
||||
returnType: String = "V",
|
||||
accessFlags: Int = AccessFlags.STATIC.value,
|
||||
registerCount: Int = 1,
|
||||
) = ImmutableMethod(
|
||||
"Ldummy;",
|
||||
name,
|
||||
emptyList(), // parameters
|
||||
returnType,
|
||||
accessFlags,
|
||||
emptySet(),
|
||||
emptySet(),
|
||||
MutableMethodImplementation(registerCount),
|
||||
).toMutable()
|
||||
|
||||
private fun assertInstructionsEqual(want: BuilderInstruction, have: BuilderInstruction) {
|
||||
assertEquals(want.opcode, have.opcode)
|
||||
assertEquals(want.format, have.format)
|
||||
assertEquals(want.codeUnits, have.codeUnits)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user