├── helper ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── io │ │ └── github │ │ └── libxposed │ │ └── helper │ │ ├── HookBuilder.java │ │ ├── Reflector.java │ │ └── Misc.java ├── proguard-rules.pro └── build.gradle.kts ├── helper-ktx ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── io │ │ └── github │ │ └── libxposed │ │ └── helper │ │ └── ktx │ │ └── HookBuilderKt.kt ├── proguard-rules.pro └── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle.kts ├── .github └── workflows │ └── android.yml ├── gradlew.bat ├── gradlew └── LICENSE /helper/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /helper-ktx/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableR8.fullMode=true 2 | android.useAndroidX=true 3 | -------------------------------------------------------------------------------- /helper-ktx/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /helper/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libxposed/helper/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /helper/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep interface io.github.libxposed.helper.HookBuilder { *; } 2 | -keep interface io.github.libxposed.helper.HookBuilder$* { *; } 3 | -keepattributes * 4 | -repackageclasses "libxposed.helper" 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | plugins { 8 | id("com.android.library") version "7.4.1" 9 | id("org.jetbrains.kotlin.android") version "1.7.20" 10 | } 11 | } 12 | 13 | @Suppress("UnstableApiUsage") 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | mavenLocal() 20 | } 21 | } 22 | rootProject.name = "helper" 23 | include(":helper", ":helper-ktx") 24 | -------------------------------------------------------------------------------- /helper-ktx/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class io.github.libxposed.helper.ktx.*Kt { public ; } 2 | -keep class io.github.libxposed.helper.ktx.DexAnalysis 3 | -keep class io.github.libxposed.helper.ktx.AnnotationAnalysis 4 | -keep class io.github.libxposed.helper.ktx.DummyHooker 5 | -keep class io.github.libxposed.helper.ktx.HookBuilderKtKt { public ; } 6 | -keep class io.github.libxposed.helper.ktx.* implements io.github.libxposed.helper.HookBuilder$* { public ; } 7 | -keep abstract class io.github.libxposed.helper.ktx.LazyBind { abstract ; (); } 8 | -keep class io.github.libxposed.helper.ktx.Hooker 9 | -keep class io.github.libxposed.helper.ktx.Matcher 10 | -keepattributes * 11 | -repackageclasses "libxposed.helper.kt" 12 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | environment: 16 | name: github-pages 17 | url: ${{ steps.deployment.outputs.page_url }} 18 | 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | submodules: 'recursive' 25 | fetch-depth: 0 26 | - name: set up JDK 17 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: '17' 30 | distribution: 'temurin' 31 | cache: gradle 32 | - name: Build dependencies 33 | run: | 34 | mkdir -p libxposed 35 | cd libxposed 36 | git clone https://github.com/libxposed/api.git 37 | cd api 38 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 39 | ./gradlew publishToMavenLocal 40 | cd ../.. 41 | env: 42 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} 43 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} 44 | - name: Build with Gradle 45 | run: | 46 | echo 'org.gradle.caching=true' >> gradle.properties 47 | echo 'org.gradle.parallel=true' >> gradle.properties 48 | echo 'org.gradle.vfs.watch=true' >> gradle.properties 49 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 50 | ./gradlew publishToMavenLocal 51 | ./gradlew --stop 52 | env: 53 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} 54 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} 55 | - name: Upload library 56 | uses: actions/upload-artifact@v3 57 | with: 58 | name: library 59 | path: ~/.m2 60 | - name: Prepare pages 61 | run: | 62 | mkdir -p docs 63 | cp -r helper/build/intermediates/java_doc_dir/release/* docs/ 64 | mkdir -p docs/ktx 65 | cp -r helper-ktx/build/intermediates/java_doc_dir/release/* docs/ktx 66 | - name: Upload pages 67 | uses: actions/upload-pages-artifact@v1 68 | with: 69 | # Upload entire repository 70 | path: 'docs' 71 | - name: Deploy to GitHub Pages 72 | id: deployment 73 | uses: actions/deploy-pages@main 74 | -------------------------------------------------------------------------------- /helper/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("maven-publish") 4 | id("signing") 5 | } 6 | 7 | android { 8 | namespace = "io.github.libxposed.helper" 9 | compileSdk = 33 10 | buildToolsVersion = "33.0.2" 11 | 12 | defaultConfig { 13 | minSdk = 21 14 | targetSdk = 33 15 | } 16 | 17 | buildFeatures { 18 | resValues = false 19 | buildConfig = false 20 | } 21 | 22 | buildTypes { 23 | release { 24 | isMinifyEnabled = true 25 | proguardFiles("proguard-rules.pro") 26 | } 27 | } 28 | 29 | publishing { 30 | singleVariant("release") { 31 | withSourcesJar() 32 | withJavadocJar() 33 | } 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_11 38 | targetCompatibility = JavaVersion.VERSION_11 39 | } 40 | } 41 | 42 | publishing { 43 | publications { 44 | register("helper") { 45 | artifactId = "helper" 46 | group = "io.github.libxposed" 47 | version = "100.0.1" 48 | pom { 49 | name.set("helper") 50 | description.set("Modern Xposed Helper") 51 | url.set("https://github.com/libxposed/helper") 52 | licenses { 53 | license { 54 | name.set("Apache License 2.0") 55 | url.set("https://github.com/libxposed/service/blob/master/LICENSE") 56 | } 57 | } 58 | developers { 59 | developer { 60 | name.set("libxposed") 61 | url.set("https://libxposed.github.io") 62 | } 63 | } 64 | scm { 65 | connection.set("scm:git:https://github.com/libxposed/helper.git") 66 | url.set("https://github.com/libxposed/helper") 67 | } 68 | } 69 | afterEvaluate { 70 | from(components.getByName("release")) 71 | } 72 | } 73 | } 74 | repositories { 75 | maven { 76 | name = "ossrh" 77 | url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 78 | credentials(PasswordCredentials::class) 79 | } 80 | maven { 81 | name = "GitHubPackages" 82 | url = uri("https://maven.pkg.github.com/libxposed/helper") 83 | credentials { 84 | username = System.getenv("GITHUB_ACTOR") 85 | password = System.getenv("GITHUB_TOKEN") 86 | } 87 | } 88 | } 89 | } 90 | 91 | dependencies { 92 | compileOnly("androidx.annotation:annotation-experimental:1.3.0") 93 | compileOnly("androidx.annotation:annotation:1.5.0") 94 | compileOnly("io.github.libxposed:api:100") 95 | } 96 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /helper-ktx/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("org.jetbrains.kotlin.android") 4 | id("maven-publish") 5 | id("signing") 6 | } 7 | 8 | android { 9 | namespace = "io.github.libxposed.helper.kt" 10 | compileSdk = 33 11 | 12 | defaultConfig { 13 | minSdk = 21 14 | targetSdk = 33 15 | } 16 | 17 | buildFeatures { 18 | resValues = false 19 | buildConfig = false 20 | } 21 | 22 | buildTypes { 23 | release { 24 | isMinifyEnabled = true 25 | proguardFiles("proguard-rules.pro") 26 | } 27 | } 28 | 29 | compileOptions { 30 | sourceCompatibility = JavaVersion.VERSION_11 31 | targetCompatibility = JavaVersion.VERSION_11 32 | } 33 | 34 | kotlinOptions { 35 | jvmTarget = "11" 36 | freeCompilerArgs = listOf( 37 | "-Xno-param-assertions", 38 | "-Xno-call-assertions", 39 | "-Xno-receiver-assertions", 40 | ) 41 | } 42 | 43 | publishing { 44 | singleVariant("release") { 45 | withSourcesJar() 46 | withJavadocJar() 47 | } 48 | } 49 | } 50 | 51 | dependencies { 52 | compileOnly("androidx.annotation:annotation:1.5.0") 53 | compileOnly("io.github.libxposed:api:100") 54 | implementation(project(":helper")) 55 | } 56 | 57 | publishing { 58 | publications { 59 | register("helperKtx") { 60 | artifactId = "helper-ktx" 61 | group = "io.github.libxposed" 62 | version = "100.0.1" 63 | pom { 64 | name.set("helper-ktx") 65 | description.set("Modern Xposed Helper for Kotlin") 66 | url.set("https://github.com/libxposed/helper") 67 | licenses { 68 | license { 69 | name.set("Apache License 2.0") 70 | url.set("https://github.com/libxposed/service/blob/master/LICENSE") 71 | } 72 | } 73 | developers { 74 | developer { 75 | name.set("libxposed") 76 | url.set("https://libxposed.github.io") 77 | } 78 | } 79 | scm { 80 | connection.set("scm:git:https://github.com/libxposed/helper.git") 81 | url.set("https://github.com/libxposed/helper") 82 | } 83 | } 84 | afterEvaluate { 85 | from(components.getByName("release")) 86 | } 87 | } 88 | } 89 | repositories { 90 | maven { 91 | name = "ossrh" 92 | url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 93 | credentials(PasswordCredentials::class) 94 | } 95 | maven { 96 | name = "GitHubPackages" 97 | url = uri("https://maven.pkg.github.com/libxposed/helper") 98 | credentials { 99 | username = System.getenv("GITHUB_ACTOR") 100 | password = System.getenv("GITHUB_TOKEN") 101 | } 102 | } 103 | } 104 | } 105 | 106 | signing { 107 | val signingKey = findProperty("signingKey") as String? 108 | val signingPassword = findProperty("signingPassword") as String? 109 | if (signingKey != null && signingPassword != null) { 110 | useInMemoryPgpKeys(signingKey, signingPassword) 111 | } 112 | sign(publishing.publications) 113 | } 114 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /helper/src/main/java/io/github/libxposed/helper/HookBuilder.java: -------------------------------------------------------------------------------- 1 | package io.github.libxposed.helper; 2 | 3 | import android.os.Build; 4 | import android.os.Handler; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.annotation.RequiresApi; 9 | import androidx.annotation.RequiresOptIn; 10 | 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.lang.annotation.ElementType; 14 | import java.lang.annotation.Retention; 15 | import java.lang.annotation.RetentionPolicy; 16 | import java.lang.annotation.Target; 17 | import java.lang.reflect.Constructor; 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Member; 20 | import java.lang.reflect.Method; 21 | import java.util.Map; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Future; 24 | 25 | import dalvik.system.BaseDexClassLoader; 26 | import io.github.libxposed.api.XposedInterface; 27 | 28 | @SuppressWarnings("unused") 29 | public interface HookBuilder { 30 | 31 | @NonNull 32 | static Future buildHooks(@NonNull XposedInterface ctx, @NonNull BaseDexClassLoader classLoader, @NonNull String sourcePath, Consumer consumer) { 33 | var builder = new HookBuilderImpl(ctx, classLoader, sourcePath); 34 | consumer.accept(builder); 35 | return builder.build(); 36 | } 37 | 38 | @DexAnalysis 39 | @NonNull 40 | HookBuilder setForceDexAnalysis(boolean forceDexAnalysis); 41 | 42 | @NonNull 43 | HookBuilder setExecutorService(@NonNull ExecutorService executorService); 44 | 45 | @NonNull 46 | HookBuilder setCallbackHandler(@NonNull Handler callbackHandler); 47 | 48 | @NonNull 49 | HookBuilder setCacheChecker(@NonNull Predicate> cacheChecker); 50 | 51 | @NonNull 52 | HookBuilder setCacheInputStream(@NonNull InputStream cacheInputStream); 53 | 54 | @NonNull 55 | HookBuilder setCacheOutputStream(@NonNull OutputStream cacheOutputStream); 56 | 57 | @NonNull 58 | HookBuilder setExceptionHandler(@NonNull Predicate handler); 59 | 60 | @NonNull 61 | MethodLazySequence methods(@NonNull Consumer matcher); 62 | 63 | @NonNull 64 | MethodMatch firstMethod(@NonNull Consumer matcher); 65 | 66 | @NonNull 67 | ConstructorLazySequence constructors(@NonNull Consumer matcher); 68 | 69 | @NonNull 70 | ConstructorMatch firstConstructor(@NonNull Consumer matcher); 71 | 72 | @NonNull 73 | FieldLazySequence fields(@NonNull Consumer matcher); 74 | 75 | @NonNull 76 | FieldMatch firstField(@NonNull Consumer matcher); 77 | 78 | @NonNull 79 | ClassLazySequence classes(@NonNull Consumer matcher); 80 | 81 | @NonNull 82 | ClassMatch firstClass(@NonNull Consumer matcher); 83 | 84 | @NonNull 85 | StringMatch exact(@NonNull String string); 86 | 87 | @NonNull 88 | StringMatch prefix(@NonNull String prefix); 89 | 90 | @NonNull 91 | StringMatch firstPrefix(@NonNull String prefix); 92 | 93 | @NonNull 94 | ClassMatch exactClass(@NonNull String name); 95 | 96 | @NonNull 97 | ClassMatch exact(@NonNull Class clazz); 98 | 99 | @NonNull 100 | MethodMatch exactMethod(@NonNull String signature); 101 | 102 | @NonNull 103 | MethodMatch exact(@NonNull Method method); 104 | 105 | @NonNull 106 | ConstructorMatch exactConstructor(@NonNull String signature); 107 | 108 | @NonNull 109 | ConstructorMatch exact(@NonNull Constructor constructor); 110 | 111 | @NonNull 112 | FieldMatch exactField(@NonNull String signature); 113 | 114 | @NonNull 115 | FieldMatch exact(@NonNull Field field); 116 | 117 | // replacement for java.lang.reflect.Parameter that is not available before Android O 118 | interface Parameter { 119 | @NonNull 120 | Class getType(); 121 | 122 | int getIndex(); 123 | 124 | @NonNull 125 | Member getDeclaringExecutable(); 126 | } 127 | 128 | @FunctionalInterface 129 | interface Supplier { 130 | @NonNull 131 | T get(); 132 | } 133 | 134 | @FunctionalInterface 135 | interface Consumer { 136 | void accept(@NonNull T t); 137 | } 138 | 139 | @FunctionalInterface 140 | interface BiConsumer { 141 | void accept(@NonNull T t, @NonNull U u); 142 | } 143 | 144 | @FunctionalInterface 145 | interface Predicate { 146 | boolean test(@NonNull T t); 147 | } 148 | 149 | @RequiresOptIn(level = RequiresOptIn.Level.ERROR) 150 | @Retention(RetentionPolicy.CLASS) 151 | @Target({ElementType.METHOD}) 152 | @interface DexAnalysis { 153 | } 154 | 155 | @RequiresOptIn(level = RequiresOptIn.Level.ERROR) 156 | @Retention(RetentionPolicy.CLASS) 157 | @Target({ElementType.METHOD}) 158 | @interface AnnotationAnalysis { 159 | } 160 | 161 | interface ReflectMatcher> { 162 | @NonNull 163 | Self setKey(@NonNull String key); 164 | 165 | @NonNull 166 | Self setIsPublic(boolean isPublic); 167 | 168 | @NonNull 169 | Self setIsPrivate(boolean isPrivate); 170 | 171 | @NonNull 172 | Self setIsProtected(boolean isProtected); 173 | 174 | @NonNull 175 | Self setIsPackage(boolean isPackage); 176 | } 177 | 178 | interface Syntax> { 179 | @NonNull 180 | Syntax and(@NonNull Syntax predicate); 181 | 182 | @NonNull 183 | Syntax or(@NonNull Syntax predicate); 184 | 185 | @NonNull 186 | Syntax not(); 187 | } 188 | 189 | interface ClassMatcher extends ReflectMatcher { 190 | @NonNull 191 | ClassMatcher setName(@NonNull StringMatch name); 192 | 193 | @NonNull 194 | ClassMatcher setSuperClass(@NonNull ClassMatch superClassMatch); 195 | 196 | @NonNull 197 | ClassMatcher setContainsInterfaces(@NonNull Syntax syntax); 198 | 199 | @NonNull 200 | ClassMatcher setIsAbstract(boolean isAbstract); 201 | 202 | @NonNull 203 | ClassMatcher setIsStatic(boolean isStatic); 204 | 205 | @NonNull 206 | ClassMatcher setIsFinal(boolean isFinal); 207 | 208 | @NonNull 209 | ClassMatcher setIsInterface(boolean isInterface); 210 | } 211 | 212 | interface ParameterMatcher extends ReflectMatcher { 213 | @NonNull 214 | ParameterMatcher setIndex(int index); 215 | 216 | @NonNull 217 | ParameterMatcher setType(@NonNull ClassMatch type); 218 | 219 | @RequiresApi(Build.VERSION_CODES.O) 220 | @NonNull 221 | ParameterMatcher setIsFinal(boolean isFinal); 222 | 223 | @RequiresApi(Build.VERSION_CODES.O) 224 | @NonNull 225 | ParameterMatcher setIsSynthetic(boolean isSynthetic); 226 | 227 | @RequiresApi(Build.VERSION_CODES.O) 228 | @NonNull 229 | ParameterMatcher setIsVarargs(boolean isVarargs); 230 | 231 | @RequiresApi(Build.VERSION_CODES.O) 232 | @NonNull 233 | ParameterMatcher setIsImplicit(boolean isImplicit); 234 | } 235 | 236 | interface MemberMatcher> extends ReflectMatcher { 237 | @NonNull 238 | Self setDeclaringClass(@NonNull ClassMatch declaringClassMatch); 239 | 240 | @NonNull 241 | Self setIsSynthetic(boolean isSynthetic); 242 | 243 | @NonNull 244 | Self setIncludeSuper(boolean includeSuper); 245 | 246 | @NonNull 247 | Self setIncludeInterface(boolean includeInterface); 248 | } 249 | 250 | interface FieldMatcher extends MemberMatcher { 251 | @NonNull 252 | FieldMatcher setName(@NonNull StringMatch name); 253 | 254 | @NonNull 255 | FieldMatcher setType(@NonNull ClassMatch type); 256 | 257 | @NonNull 258 | FieldMatcher setIsStatic(boolean isStatic); 259 | 260 | @NonNull 261 | FieldMatcher setIsFinal(boolean isFinal); 262 | 263 | @NonNull 264 | FieldMatcher setIsTransient(boolean isTransient); 265 | 266 | @NonNull 267 | FieldMatcher setIsVolatile(boolean isVolatile); 268 | } 269 | 270 | interface ExecutableMatcher> extends MemberMatcher { 271 | @NonNull 272 | Self setParameterCount(int count); 273 | 274 | @NonNull 275 | Self setParameters(@NonNull Syntax parameters); 276 | 277 | @DexAnalysis 278 | @NonNull 279 | Self setReferredStrings(@NonNull Syntax referredStrings); 280 | 281 | @DexAnalysis 282 | @NonNull 283 | Self setAssignedFields(@NonNull Syntax assignedFields); 284 | 285 | @DexAnalysis 286 | @NonNull 287 | Self setAccessedFields(@NonNull Syntax accessedFields); 288 | 289 | @DexAnalysis 290 | @NonNull 291 | Self setInvokedMethods(@NonNull Syntax invokedMethods); 292 | 293 | @DexAnalysis 294 | @NonNull 295 | Self setInvokedConstructors(@NonNull Syntax invokedConstructors); 296 | 297 | @DexAnalysis 298 | @NonNull 299 | Self setContainsOpcodes(@NonNull byte[] opcodes); 300 | 301 | @NonNull 302 | Self setIsVarargs(boolean isVarargs); 303 | 304 | @NonNull 305 | Syntax conjunction(@NonNull ClassMatch... types); 306 | 307 | @NonNull 308 | Syntax conjunction(@NonNull Class... types); 309 | 310 | @NonNull 311 | Syntax observe(int index, @NonNull ClassMatch types); 312 | 313 | @NonNull 314 | Syntax observe(int index, @NonNull Class types); 315 | 316 | @NonNull 317 | ParameterMatch firstParameter(@NonNull Consumer consumer); 318 | 319 | @NonNull 320 | ParameterLazySequence parameters(@NonNull Consumer consumer); 321 | } 322 | 323 | interface MethodMatcher extends ExecutableMatcher { 324 | @NonNull 325 | MethodMatcher setName(@NonNull StringMatch name); 326 | 327 | @NonNull 328 | MethodMatcher setReturnType(@NonNull ClassMatch returnType); 329 | 330 | @NonNull 331 | MethodMatcher setIsAbstract(boolean isAbstract); 332 | 333 | @NonNull 334 | MethodMatcher setIsStatic(boolean isStatic); 335 | 336 | @NonNull 337 | MethodMatcher setIsFinal(boolean isFinal); 338 | 339 | @NonNull 340 | MethodMatcher setIsSynchronized(boolean isSynchronized); 341 | 342 | @NonNull 343 | MethodMatcher setIsNative(boolean isNative); 344 | } 345 | 346 | interface ConstructorMatcher extends ExecutableMatcher { 347 | } 348 | 349 | interface BaseMatch, Reflect> { 350 | @NonNull 351 | Syntax observe(); 352 | 353 | @NonNull 354 | Syntax reverse(); 355 | } 356 | 357 | interface ReflectMatch, Reflect, Matcher extends ReflectMatcher> extends BaseMatch { 358 | @Nullable 359 | String getKey(); 360 | 361 | @NonNull 362 | Self setKey(@Nullable String key); 363 | 364 | @NonNull 365 | Self onMatch(@NonNull Consumer consumer); 366 | 367 | @NonNull 368 | Self onMiss(@NonNull Runnable handler); 369 | 370 | @NonNull 371 | Self substituteIfMiss(@NonNull Supplier substitute); 372 | 373 | @NonNull 374 | Self matchFirstIfMiss(@NonNull Consumer consumer); 375 | 376 | @NonNull 377 | Self bind(@NonNull Bind bind, @NonNull BiConsumer consumer); 378 | } 379 | 380 | interface ClassMatch extends ReflectMatch, ClassMatcher> { 381 | @NonNull 382 | ClassMatch getSuperClass(); 383 | 384 | @NonNull 385 | ClassLazySequence getInterfaces(); 386 | 387 | @NonNull 388 | MethodLazySequence getDeclaredMethods(); 389 | 390 | @NonNull 391 | ConstructorLazySequence getDeclaredConstructors(); 392 | 393 | @NonNull 394 | FieldLazySequence getDeclaredFields(); 395 | 396 | @NonNull 397 | ClassMatch getArrayType(); 398 | } 399 | 400 | interface ParameterMatch extends ReflectMatch { 401 | @NonNull 402 | ClassMatch getType(); 403 | } 404 | 405 | interface MemberMatch, Reflect extends Member, Matcher extends MemberMatcher> extends ReflectMatch { 406 | @NonNull 407 | ClassMatch getDeclaringClass(); 408 | } 409 | 410 | interface ExecutableMatch, Reflect extends Member, Matcher extends ExecutableMatcher> extends MemberMatch { 411 | @NonNull 412 | ClassLazySequence getParameterTypes(); 413 | 414 | @NonNull 415 | ParameterLazySequence getParameters(); 416 | 417 | @DexAnalysis 418 | @NonNull 419 | FieldLazySequence getAssignedFields(); 420 | 421 | @DexAnalysis 422 | @NonNull 423 | FieldLazySequence getAccessedFields(); 424 | 425 | @DexAnalysis 426 | @NonNull 427 | MethodLazySequence getInvokedMethods(); 428 | 429 | @DexAnalysis 430 | @NonNull 431 | ConstructorLazySequence getInvokedConstructors(); 432 | } 433 | 434 | interface MethodMatch extends ExecutableMatch { 435 | @NonNull 436 | ClassMatch getReturnType(); 437 | } 438 | 439 | interface ConstructorMatch extends ExecutableMatch, ConstructorMatcher> { 440 | } 441 | 442 | interface FieldMatch extends MemberMatch { 443 | @NonNull 444 | ClassMatch getType(); 445 | } 446 | 447 | interface StringMatch extends BaseMatch { 448 | 449 | } 450 | 451 | interface LazySequence, Match extends ReflectMatch, Reflect, Matcher extends ReflectMatcher> { 452 | @NonNull 453 | Match first(); 454 | 455 | @NonNull 456 | Match first(@NonNull Consumer consumer); 457 | 458 | @NonNull 459 | Self all(@NonNull Consumer consumer); 460 | 461 | @NonNull 462 | Self onMatch(@NonNull Consumer> consumer); 463 | 464 | @NonNull 465 | Self onMiss(@NonNull Runnable runnable); 466 | 467 | @NonNull 468 | Syntax conjunction(); 469 | 470 | @NonNull 471 | Syntax disjunction(); 472 | 473 | @NonNull 474 | Self substituteIfMiss(@NonNull Supplier substitute); 475 | 476 | @NonNull 477 | Self matchIfMiss(@NonNull Consumer consumer); 478 | 479 | @NonNull 480 | Self bind(@NonNull Bind bind, @NonNull BiConsumer> consumer); 481 | } 482 | 483 | interface ClassLazySequence extends LazySequence, ClassMatcher> { 484 | @NonNull 485 | MethodLazySequence methods(@NonNull Consumer matcher); 486 | 487 | @NonNull 488 | MethodMatch firstMethod(@NonNull Consumer matcher); 489 | 490 | @NonNull 491 | ConstructorLazySequence constructors(@NonNull Consumer matcher); 492 | 493 | @NonNull 494 | ConstructorMatch firstConstructor(@NonNull Consumer matcher); 495 | 496 | @NonNull 497 | FieldLazySequence fields(@NonNull Consumer matcher); 498 | 499 | @NonNull 500 | FieldMatch firstField(@NonNull Consumer matcher); 501 | } 502 | 503 | interface ParameterLazySequence extends LazySequence { 504 | @NonNull 505 | ClassLazySequence types(@NonNull Consumer matcher); 506 | 507 | @NonNull 508 | ClassMatch firstType(@NonNull Consumer matcher); 509 | } 510 | 511 | interface MemberLazySequence, Match extends MemberMatch, Reflect extends Member, Matcher extends MemberMatcher> extends LazySequence { 512 | @NonNull 513 | ClassLazySequence declaringClasses(@NonNull Consumer matcher); 514 | 515 | @NonNull 516 | ClassMatch firstDeclaringClass(@NonNull Consumer matcher); 517 | } 518 | 519 | interface FieldLazySequence extends MemberLazySequence { 520 | @NonNull 521 | ClassLazySequence types(@NonNull Consumer matcher); 522 | 523 | @NonNull 524 | ClassMatch firstType(@NonNull Consumer matcher); 525 | } 526 | 527 | interface ExecutableLazySequence, Match extends ExecutableMatch, Reflect extends Member, Matcher extends ExecutableMatcher> extends MemberLazySequence { 528 | @NonNull 529 | ParameterLazySequence parameters(@NonNull Consumer matcher); 530 | 531 | @NonNull 532 | ParameterMatch firstParameter(@NonNull Consumer matcher); 533 | } 534 | 535 | interface MethodLazySequence extends ExecutableLazySequence { 536 | @NonNull 537 | ClassLazySequence returnTypes(@NonNull Consumer matcher); 538 | 539 | @NonNull 540 | ClassMatch firstReturnType(@NonNull Consumer matcher); 541 | } 542 | 543 | interface ConstructorLazySequence extends ExecutableLazySequence, ConstructorMatcher> { 544 | } 545 | 546 | interface LazyBind { 547 | void onMatch(); 548 | 549 | void onMiss(); 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /helper/src/main/java/io/github/libxposed/helper/Reflector.java: -------------------------------------------------------------------------------- 1 | package io.github.libxposed.helper; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import java.lang.ref.WeakReference; 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.Method; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | 17 | @SuppressWarnings("unused") 18 | final class Reflector { 19 | private static final Map abbreviationMap = Map.of("int", 'I', "boolean", 'Z', "float", 'F', "long", 'J', "short", 'S', "byte", 'B', "double", 'D', "char", 'C', "void", 'V'); 20 | private static final Map> primitiveClassMap = Map.of('I', int.class, 'Z', boolean.class, 'F', float.class, 'J', long.class, 'S', short.class, 'B', byte.class, 'D', double.class, 'C', char.class, 'V', void.class); 21 | private static final WeakReference EMPTY = new WeakReference<>(null); 22 | private final ClassLoader classLoader; 23 | private final HashMap>> classCache = new HashMap<>(); 24 | private final HashMap> methodCache = new HashMap<>(); 25 | private final HashMap> fieldCache = new HashMap<>(); 26 | private final HashMap>> constructorCache = new HashMap<>(); 27 | 28 | Reflector(ClassLoader classLoader) { 29 | this.classLoader = classLoader; 30 | } 31 | 32 | @NonNull 33 | Class loadClass(@NonNull String className) throws ClassNotFoundException { 34 | className = className.trim().replace('/', '.'); 35 | if (className.startsWith("L") && className.endsWith(";")) { 36 | className = className.substring(1, className.length() - 1); 37 | } else if (className.endsWith("[]")) { 38 | var sb = new StringBuilder(); 39 | while (className.endsWith("[]")) { 40 | className = className.substring(0, className.length() - 2); 41 | sb.append('['); 42 | } 43 | var abbr = abbreviationMap.get(className); 44 | if (abbr != null) { 45 | sb.append(abbr); 46 | } else { 47 | sb.append('L').append(className).append(';'); 48 | } 49 | className = sb.toString(); 50 | } 51 | try { 52 | WeakReference> ref; 53 | synchronized (classCache) { 54 | ref = classCache.get(className); 55 | } 56 | if (ref == EMPTY) { 57 | throw new ClassNotFoundException(className); 58 | } 59 | var clazz = ref != null ? ref.get() : null; 60 | if (clazz == null) { 61 | try { 62 | clazz = Class.forName(className, false, classLoader); 63 | } catch (ClassNotFoundException e) { 64 | synchronized (classCache) { 65 | //noinspection unchecked 66 | classCache.put(className, (WeakReference>) EMPTY); 67 | } 68 | throw e; 69 | } 70 | synchronized (classCache) { 71 | classCache.put(className, new WeakReference<>(clazz)); 72 | } 73 | } 74 | return clazz; 75 | } catch (ClassNotFoundException e) { 76 | final int lastDot = className.lastIndexOf('.'); 77 | if (lastDot > 0) { 78 | try { 79 | final String innerClassName = className.substring(0, lastDot) + '$' + className.substring(lastDot + 1); 80 | return loadClass(innerClassName); 81 | } catch (ClassNotFoundException ignored) { 82 | } 83 | } 84 | throw e; 85 | } 86 | } 87 | 88 | @NonNull 89 | Field loadField(@NonNull String fieldString) throws ClassNotFoundException, NoSuchFieldException { 90 | Class declaringClass; 91 | String name; 92 | Class type = null; 93 | var sep = fieldString.lastIndexOf("->"); 94 | if (sep > 0) { 95 | var className = fieldString.substring(0, sep); 96 | var signature = fieldString.substring(sep + 2); 97 | declaringClass = loadClass(className); 98 | sep = signature.indexOf(':'); 99 | if (sep > 0) { 100 | var typeName = signature.substring(sep + 1); 101 | type = primitiveClassMap.get(typeName.charAt(0)); 102 | if (type == null) { 103 | type = loadClass(typeName); 104 | } 105 | } else { 106 | sep = signature.length(); 107 | } 108 | name = signature.substring(0, sep); 109 | } else { 110 | var lastSpace = fieldString.lastIndexOf(' '); 111 | if (lastSpace > 0) { 112 | var secondLastSpace = fieldString.lastIndexOf(' ', lastSpace - 1); 113 | var typeName = fieldString.substring(secondLastSpace + 1, lastSpace); 114 | type = primitiveClassMap.get(typeName.charAt(0)); 115 | if (type == null) { 116 | type = loadClass(typeName); 117 | } 118 | } 119 | var lastDot = fieldString.lastIndexOf('.'); 120 | if (lastDot < 0) { 121 | throw new NoSuchFieldException(fieldString); 122 | } 123 | declaringClass = loadClass(fieldString.substring(lastSpace + 1, lastDot)); 124 | name = fieldString.substring(lastDot + 1); 125 | } 126 | var key = new MemberKey.Field(declaringClass, name); 127 | WeakReference ref; 128 | synchronized (fieldCache) { 129 | ref = fieldCache.get(key); 130 | } 131 | if (ref == EMPTY) { 132 | throw new NoSuchFieldException(fieldString); 133 | } 134 | var field = ref != null ? ref.get() : null; 135 | if (field == null) { 136 | try { 137 | field = declaringClass.getDeclaredField(name); 138 | if (type != null && field.getType() != type) { 139 | throw new NoSuchFieldException(fieldString); 140 | } 141 | } catch (NoSuchFieldException e) { 142 | synchronized (fieldCache) { 143 | //noinspection unchecked 144 | fieldCache.put(key, (WeakReference) EMPTY); 145 | } 146 | throw e; 147 | } 148 | field.setAccessible(true); 149 | synchronized (fieldCache) { 150 | fieldCache.put(key, new WeakReference<>(field)); 151 | } 152 | } 153 | return field; 154 | } 155 | 156 | @NonNull 157 | Method loadMethod(@NonNull String methodString) throws ClassNotFoundException, NoSuchMethodException { 158 | Class declaringClass; 159 | String name; 160 | ArrayList> parameterTypes = new ArrayList<>(); 161 | Class returnType = null; 162 | var sep = methodString.lastIndexOf("->"); 163 | if (sep > 0) { 164 | var className = methodString.substring(0, sep); 165 | var signature = methodString.substring(sep + 2); 166 | declaringClass = loadClass(className); 167 | var start = signature.indexOf('('); 168 | var end = signature.lastIndexOf(')'); 169 | if (start < 0 || end < 0 || end < start) { 170 | throw new NoSuchMethodException(methodString); 171 | } 172 | var returnTypeName = signature.substring(end + 1).trim(); 173 | if (!returnTypeName.isEmpty()) { 174 | returnType = primitiveClassMap.get(returnTypeName.charAt(0)); 175 | if (returnType == null) { 176 | returnType = loadClass(returnTypeName); 177 | } 178 | } 179 | name = signature.substring(0, start); 180 | var params = signature.substring(start + 1, end); 181 | int idx = 0; 182 | while (idx < params.length()) { 183 | var ch = params.charAt(idx); 184 | String paramName; 185 | if (ch == 'L') { 186 | var endIdx = params.indexOf(';', idx); 187 | if (endIdx < 0) { 188 | throw new NoSuchMethodException(methodString); 189 | } 190 | paramName = params.substring(idx, endIdx + 1); 191 | idx = endIdx + 1; 192 | } else if (ch == '[') { 193 | var endIdx = idx + 1; 194 | while (endIdx < params.length() && params.charAt(endIdx) == '[') { 195 | endIdx++; 196 | } 197 | if (endIdx >= params.length()) { 198 | throw new NoSuchMethodException(methodString); 199 | } 200 | ch = params.charAt(endIdx); 201 | if (ch == 'L') { 202 | endIdx = params.indexOf(';', endIdx); 203 | } 204 | if (endIdx >= params.length()) { 205 | throw new NoSuchMethodException(methodString); 206 | } 207 | paramName = params.substring(idx, endIdx + 1); 208 | idx = endIdx + 1; 209 | } else { 210 | parameterTypes.add(primitiveClassMap.get(ch)); 211 | idx++; 212 | continue; 213 | } 214 | parameterTypes.add(loadClass(paramName)); 215 | } 216 | } else { 217 | var start = methodString.indexOf('('); 218 | var end = methodString.lastIndexOf(')'); 219 | if (start < 0 || end < 0 || end < start) { 220 | throw new NoSuchMethodException(methodString); 221 | } 222 | var lastSpace = methodString.lastIndexOf(' ', start); 223 | if (lastSpace > 0) { 224 | var secondLastSpace = methodString.lastIndexOf(' ', lastSpace - 1); 225 | var returnTypeName = methodString.substring(secondLastSpace + 1, lastSpace); 226 | var returnTypeAbbr = abbreviationMap.get(returnTypeName); 227 | if (returnTypeAbbr != null) { 228 | returnType = primitiveClassMap.get(returnTypeAbbr); 229 | } else { 230 | returnType = loadClass(returnTypeName); 231 | } 232 | } 233 | var lastDot = methodString.lastIndexOf('.', start); 234 | if (lastDot < 0) { 235 | throw new NoSuchMethodException(methodString); 236 | } 237 | name = methodString.substring(lastDot + 1, start); 238 | declaringClass = loadClass(methodString.substring(lastSpace + 1, lastDot)); 239 | var params = methodString.substring(start + 1, end); 240 | int idx = 0; 241 | while (idx < params.length()) { 242 | var nextComma = params.indexOf(',', idx); 243 | if (nextComma < 0) { 244 | nextComma = params.length(); 245 | } 246 | var paramName = params.substring(idx, nextComma).trim(); 247 | idx = nextComma + 1; 248 | var paramAbbr = abbreviationMap.get(paramName); 249 | if (paramAbbr != null) { 250 | parameterTypes.add(primitiveClassMap.get(paramAbbr)); 251 | } else { 252 | parameterTypes.add(loadClass(paramName)); 253 | } 254 | } 255 | } 256 | var parameterTypesArray = parameterTypes.toArray(new Class[0]); 257 | var key = new MemberKey.Method(declaringClass, name, parameterTypesArray); 258 | WeakReference ref; 259 | synchronized (methodCache) { 260 | ref = methodCache.get(key); 261 | } 262 | if (ref == EMPTY) { 263 | throw new NoSuchMethodException(methodString); 264 | } 265 | Method method = ref == null ? null : ref.get(); 266 | if (method == null) { 267 | try { 268 | method = declaringClass.getDeclaredMethod(name, parameterTypesArray); 269 | if (returnType != null && !returnType.equals(method.getReturnType())) { 270 | throw new NoSuchMethodException(methodString); 271 | } 272 | } catch (NoSuchMethodException e) { 273 | synchronized (methodCache) { 274 | //noinspection unchecked 275 | methodCache.put(key, (WeakReference) EMPTY); 276 | } 277 | throw e; 278 | } 279 | method.setAccessible(true); 280 | synchronized (methodCache) { 281 | methodCache.put(key, new WeakReference<>(method)); 282 | } 283 | } 284 | return method; 285 | } 286 | 287 | @NonNull 288 | Constructor loadConstructor(@NonNull String constructorString) throws ClassNotFoundException, NoSuchMethodException { 289 | Class declaringClass; 290 | ArrayList> parameterTypes = new ArrayList<>(); 291 | var sep = constructorString.lastIndexOf("->"); 292 | if (sep > 0) { 293 | var className = constructorString.substring(0, sep); 294 | var signature = constructorString.substring(sep + 2); 295 | declaringClass = loadClass(className); 296 | var start = signature.indexOf('('); 297 | var end = signature.lastIndexOf(')'); 298 | if (start < 0 || end < 0 || end < start) { 299 | throw new NoSuchMethodException(constructorString); 300 | } 301 | var returnTypeName = signature.substring(end + 1).trim(); 302 | if (!returnTypeName.isEmpty() && !"V".equals(returnTypeName)) { 303 | throw new NoSuchMethodException(constructorString); 304 | } 305 | var name = signature.substring(0, start); 306 | if (!"".equals(name)) { 307 | throw new NoSuchMethodException(constructorString); 308 | } 309 | var params = signature.substring(start + 1, end); 310 | int idx = 0; 311 | while (idx < params.length()) { 312 | var ch = params.charAt(idx); 313 | String paramName; 314 | if (ch == 'L') { 315 | var endIdx = params.indexOf(';', idx); 316 | if (endIdx < 0) { 317 | throw new NoSuchMethodException(constructorString); 318 | } 319 | paramName = params.substring(idx, endIdx + 1); 320 | idx = endIdx + 1; 321 | } else if (ch == '[') { 322 | var endIdx = idx + 1; 323 | while (endIdx < params.length() && params.charAt(endIdx) == '[') { 324 | endIdx++; 325 | } 326 | if (endIdx >= params.length()) { 327 | throw new NoSuchMethodException(constructorString); 328 | } 329 | ch = params.charAt(endIdx); 330 | if (ch == 'L') { 331 | endIdx = params.indexOf(';', endIdx); 332 | } 333 | if (endIdx >= params.length()) { 334 | throw new NoSuchMethodException(constructorString); 335 | } 336 | paramName = params.substring(idx, endIdx + 1); 337 | idx = endIdx + 1; 338 | } else { 339 | parameterTypes.add(primitiveClassMap.get(ch)); 340 | idx++; 341 | continue; 342 | } 343 | parameterTypes.add(loadClass(paramName)); 344 | } 345 | } else { 346 | var start = constructorString.indexOf('('); 347 | var end = constructorString.lastIndexOf(')'); 348 | if (start < 0 || end < 0 || end < start) { 349 | throw new NoSuchMethodException(constructorString); 350 | } 351 | declaringClass = loadClass(constructorString.substring(0, start)); 352 | var params = constructorString.substring(start + 1, end); 353 | int idx = 0; 354 | while (idx < params.length()) { 355 | var nextComma = params.indexOf(',', idx); 356 | if (nextComma < 0) { 357 | nextComma = params.length(); 358 | } 359 | var paramName = params.substring(idx, nextComma).trim(); 360 | idx = nextComma + 1; 361 | var paramAbbr = abbreviationMap.get(paramName); 362 | if (paramAbbr != null) { 363 | parameterTypes.add(primitiveClassMap.get(paramAbbr)); 364 | } else { 365 | parameterTypes.add(loadClass(paramName)); 366 | } 367 | } 368 | } 369 | var parameterTypesArray = parameterTypes.toArray(new Class[0]); 370 | var key = new MemberKey.Constructor(declaringClass, parameterTypesArray); 371 | WeakReference> ref; 372 | synchronized (constructorCache) { 373 | ref = constructorCache.get(key); 374 | } 375 | if (ref == EMPTY) { 376 | throw new NoSuchMethodException(constructorString); 377 | } 378 | Constructor constructor = ref == null ? null : ref.get(); 379 | if (constructor == null) { 380 | try { 381 | constructor = declaringClass.getDeclaredConstructor(parameterTypesArray); 382 | } catch (NoSuchMethodException e) { 383 | synchronized (constructorCache) { 384 | //noinspection unchecked 385 | constructorCache.put(key, (WeakReference>) EMPTY); 386 | } 387 | throw e; 388 | } 389 | constructor.setAccessible(true); 390 | synchronized (constructorCache) { 391 | constructorCache.put(key, new WeakReference<>(constructor)); 392 | } 393 | } 394 | return constructor; 395 | } 396 | 397 | @NonNull 398 | Collection> loadClasses(Collection classNames) throws ClassNotFoundException { 399 | ArrayList> classes = new ArrayList<>(); 400 | for (String className : classNames) { 401 | classes.add(loadClass(className)); 402 | } 403 | return classes; 404 | } 405 | 406 | @NonNull 407 | Collection loadFields(Collection fieldStrings) throws ClassNotFoundException, NoSuchFieldException { 408 | ArrayList fields = new ArrayList<>(); 409 | for (String fieldString : fieldStrings) { 410 | fields.add(loadField(fieldString)); 411 | } 412 | return fields; 413 | } 414 | 415 | @NonNull 416 | Collection loadMethods(Collection methodStrings) throws ClassNotFoundException, NoSuchMethodException { 417 | ArrayList methods = new ArrayList<>(); 418 | for (String methodString : methodStrings) { 419 | methods.add(loadMethod(methodString)); 420 | } 421 | return methods; 422 | } 423 | 424 | @NonNull 425 | Collection> loadConstructors(Collection constructorStrings) throws ClassNotFoundException, NoSuchMethodException { 426 | ArrayList> constructors = new ArrayList<>(); 427 | for (String constructorString : constructorStrings) { 428 | constructors.add(loadConstructor(constructorString)); 429 | } 430 | return constructors; 431 | } 432 | 433 | abstract static class MemberKey { 434 | private final int hash; 435 | 436 | protected MemberKey(int hash) { 437 | this.hash = hash; 438 | } 439 | 440 | protected static String[] getClassNames(Class[] classes) { 441 | String[] classNames = new String[classes.length]; 442 | for (int i = 0; i < classes.length; i++) { 443 | classNames[i] = classes[i].getName(); 444 | } 445 | return classNames; 446 | } 447 | 448 | @Override 449 | public int hashCode() { 450 | return hash; 451 | } 452 | 453 | @Override 454 | public abstract boolean equals(@Nullable Object obj); 455 | 456 | static class Field extends MemberKey { 457 | private final String declaringClass; 458 | private final String name; 459 | 460 | protected Field(String declaringClass, String name) { 461 | super(Objects.hash(declaringClass, name)); 462 | this.declaringClass = declaringClass; 463 | this.name = name; 464 | } 465 | 466 | protected Field(Class declaringClass, String name) { 467 | this(declaringClass.getName(), name); 468 | } 469 | 470 | @Override 471 | public boolean equals(@Nullable Object obj) { 472 | if (obj == this) { 473 | return true; 474 | } 475 | if (obj instanceof Field) { 476 | Field other = (Field) obj; 477 | return Objects.equals(other.declaringClass, declaringClass) && Objects.equals(other.name, name); 478 | } 479 | return false; 480 | } 481 | } 482 | 483 | static class Constructor extends MemberKey { 484 | protected final String declaringClass; 485 | protected final String[] parameterTypes; 486 | 487 | protected Constructor(String declaringClass, String[] parameterTypes) { 488 | super(31 * Objects.hash(declaringClass) + Arrays.hashCode(parameterTypes)); 489 | this.declaringClass = declaringClass; 490 | this.parameterTypes = parameterTypes; 491 | } 492 | 493 | protected Constructor(Class declaringClass, Class[] parameterTypes) { 494 | this(declaringClass.getName(), getClassNames(parameterTypes)); 495 | } 496 | 497 | @Override 498 | public boolean equals(@Nullable Object obj) { 499 | if (obj == this) { 500 | return true; 501 | } 502 | if (obj instanceof Constructor) { 503 | Constructor other = (Constructor) obj; 504 | return Objects.equals(other.declaringClass, declaringClass) && Arrays.equals(other.parameterTypes, parameterTypes); 505 | } 506 | return false; 507 | } 508 | } 509 | 510 | static class Method extends MemberKey { 511 | protected final String declaringClass; 512 | protected final String name; 513 | protected final String[] parameterTypes; 514 | 515 | protected Method(String declaringClass, String name, String[] parameterTypes) { 516 | super(31 * Objects.hash(declaringClass, name) + Arrays.hashCode(parameterTypes)); 517 | this.declaringClass = declaringClass; 518 | this.name = name; 519 | this.parameterTypes = parameterTypes; 520 | } 521 | 522 | protected Method(Class declaringClass, String name, Class[] parameterTypes) { 523 | this(declaringClass.getName(), name, getClassNames(parameterTypes)); 524 | } 525 | 526 | @Override 527 | public boolean equals(@Nullable Object obj) { 528 | if (obj == this) { 529 | return true; 530 | } 531 | if (obj instanceof Method) { 532 | Method other = (Method) obj; 533 | return Objects.equals(other.declaringClass, declaringClass) && Objects.equals(other.name, name) && Arrays.equals(other.parameterTypes, parameterTypes); 534 | } 535 | return false; 536 | } 537 | } 538 | } 539 | } 540 | -------------------------------------------------------------------------------- /helper/src/main/java/io/github/libxposed/helper/Misc.java: -------------------------------------------------------------------------------- 1 | package io.github.libxposed.helper; 2 | 3 | import android.os.Handler; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import java.lang.reflect.Member; 9 | import java.util.AbstractMap; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.Comparator; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.Iterator; 16 | import java.util.NavigableSet; 17 | import java.util.Objects; 18 | import java.util.Queue; 19 | import java.util.Set; 20 | import java.util.SortedSet; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.ConcurrentLinkedQueue; 24 | import java.util.concurrent.ExecutionException; 25 | import java.util.concurrent.ExecutorService; 26 | import java.util.concurrent.Future; 27 | import java.util.concurrent.FutureTask; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.TimeoutException; 30 | import java.util.concurrent.atomic.AtomicReference; 31 | 32 | import io.github.libxposed.api.utils.DexParser; 33 | 34 | interface BaseObserver { 35 | void update(T result); 36 | } 37 | 38 | interface ItemObserver extends BaseObserver { 39 | void update(@Nullable T result); 40 | } 41 | 42 | interface ListObserver extends BaseObserver> { 43 | void update(@NonNull Collection result); 44 | } 45 | 46 | interface Transformer { 47 | @NonNull 48 | U transform(@NonNull T input); 49 | } 50 | 51 | interface FieldAndMethodVisitor extends DexParser.MethodVisitor, DexParser.FieldVisitor { 52 | } 53 | 54 | final class TypeOnlyParameter implements HookBuilder.Parameter { 55 | 56 | final int index; 57 | @NonNull 58 | final private Class type; 59 | 60 | TypeOnlyParameter(int index, @NonNull Class type) { 61 | this.type = type; 62 | this.index = index; 63 | } 64 | 65 | @NonNull 66 | @Override 67 | public Class getType() { 68 | return type; 69 | } 70 | 71 | @Override 72 | public int getIndex() { 73 | return index; 74 | } 75 | 76 | @NonNull 77 | @Override 78 | public Member getDeclaringExecutable() { 79 | throw new IllegalStateException("TypeOnlyParameter does not have a declaring executable"); 80 | } 81 | } 82 | 83 | final class ParameterImpl implements HookBuilder.Parameter { 84 | final private int modifiers; 85 | @NonNull 86 | final private Class type; 87 | 88 | final private int index; 89 | 90 | @NonNull 91 | final private Member declaringExecutable; 92 | 93 | ParameterImpl(int index, @NonNull Class type, @NonNull Member declaringExecutable, int modifiers) { 94 | this.type = type; 95 | this.index = index; 96 | this.declaringExecutable = declaringExecutable; 97 | this.modifiers = modifiers; 98 | } 99 | 100 | @NonNull 101 | @Override 102 | public Class getType() { 103 | return type; 104 | } 105 | 106 | @Override 107 | public int getIndex() { 108 | return 0; 109 | } 110 | 111 | @NonNull 112 | @Override 113 | public Member getDeclaringExecutable() { 114 | return declaringExecutable; 115 | } 116 | 117 | int getModifiers() { 118 | return modifiers; 119 | } 120 | 121 | @Override 122 | public int hashCode() { 123 | return Objects.hash(index, type, declaringExecutable, modifiers); 124 | } 125 | 126 | @Override 127 | public boolean equals(@Nullable Object obj) { 128 | if (obj == null) return false; 129 | else if (obj instanceof TypeOnlyParameter) { 130 | TypeOnlyParameter object = (TypeOnlyParameter) obj; 131 | return index == object.index && Objects.equals(type, object.getType()); 132 | } else if (!(obj instanceof ParameterImpl)) return false; 133 | ParameterImpl object = (ParameterImpl) obj; 134 | return index == object.index && Objects.equals(type, object.type) && Objects.equals(declaringExecutable, object.declaringExecutable) && modifiers == object.modifiers; 135 | } 136 | } 137 | 138 | abstract class SimpleExecutor { 139 | private final Queue> allTasks = new ConcurrentLinkedQueue<>(); 140 | 141 | 142 | static SimpleExecutor of(@NonNull ExecutorService executor) { 143 | return new SimpleExecutor() { 144 | @Override 145 | Future onSubmit(Callable task) { 146 | return executor.submit(task); 147 | } 148 | }; 149 | } 150 | 151 | static SimpleExecutor of(@NonNull Handler handler) { 152 | return new SimpleExecutor() { 153 | @Override 154 | Future onSubmit(Callable task) { 155 | var t = new FutureTask(task); 156 | handler.post(t); 157 | return t; 158 | } 159 | }; 160 | } 161 | 162 | abstract Future onSubmit(Callable task); 163 | 164 | Future submit(Callable task) { 165 | Future f = onSubmit(task); 166 | allTasks.add(f); 167 | return f; 168 | } 169 | 170 | final Future submit(Runnable task) { 171 | return submit(() -> { 172 | task.run(); 173 | return null; 174 | }); 175 | } 176 | 177 | final void joinAll() throws ExecutionException, InterruptedException { 178 | while (!allTasks.isEmpty()) { 179 | var task = allTasks.poll(); 180 | if (task != null) task.get(); 181 | } 182 | } 183 | 184 | final void joinAll(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { 185 | var nanos = unit.toNanos(timeout); 186 | var now = System.nanoTime(); 187 | while (!allTasks.isEmpty()) { 188 | var task = allTasks.poll(); 189 | var last = now; 190 | now = System.nanoTime(); 191 | nanos -= now - last; 192 | if (nanos < 0) throw new TimeoutException(); 193 | if (task != null) 194 | task.get(unit.convert(nanos, TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS); 195 | } 196 | } 197 | } 198 | 199 | final class PendingExecutor extends SimpleExecutor { 200 | @NonNull 201 | final Queue> pendingTasks = new ConcurrentLinkedQueue<>(); 202 | 203 | @Override 204 | Future onSubmit(Callable task) { 205 | FutureTask futureTask = new FutureTask<>(task); 206 | pendingTasks.add(futureTask); 207 | return futureTask; 208 | } 209 | } 210 | 211 | final class MatchCache { 212 | @NonNull 213 | HashMap cacheInfo = new HashMap<>(); 214 | 215 | @NonNull 216 | ConcurrentHashMap> classListCache = new ConcurrentHashMap<>(); 217 | @NonNull 218 | ConcurrentHashMap> fieldListCache = new ConcurrentHashMap<>(); 219 | @NonNull 220 | ConcurrentHashMap> methodListCache = new ConcurrentHashMap<>(); 221 | @NonNull 222 | ConcurrentHashMap> constructorListCache = new ConcurrentHashMap<>(); 223 | @NonNull 224 | ConcurrentHashMap>> parameterListCache = new ConcurrentHashMap<>(); 225 | 226 | @NonNull 227 | ConcurrentHashMap classCache = new ConcurrentHashMap<>(); 228 | @NonNull 229 | ConcurrentHashMap fieldCache = new ConcurrentHashMap<>(); 230 | @NonNull 231 | ConcurrentHashMap methodCache = new ConcurrentHashMap<>(); 232 | @NonNull 233 | ConcurrentHashMap constructorCache = new ConcurrentHashMap<>(); 234 | @NonNull 235 | ConcurrentHashMap> parameterCache = new ConcurrentHashMap<>(); 236 | } 237 | 238 | final class TreeSetView> implements Set, SortedSet, NavigableSet { 239 | final private T[] array; 240 | // array[start, end); 241 | final private int start; 242 | final private int end; 243 | 244 | private TreeSetView(T[] array, int start, int end) { 245 | this.array = array; 246 | this.start = start; 247 | this.end = end; 248 | } 249 | 250 | static > TreeSetView ofSorted(T[] array) { 251 | return new TreeSetView<>(array, 0, array.length); 252 | } 253 | 254 | static > TreeSetView ofSorted(T[] array, int start, int end) { 255 | return new TreeSetView<>(array, start, end); 256 | } 257 | 258 | static > TreeSetView ofSorted(Collection c) { 259 | //noinspection unchecked 260 | return new TreeSetView<>((T[]) c.toArray(new Comparable[0]), 0, c.size()); 261 | } 262 | 263 | @Override 264 | public Comparator comparator() { 265 | return null; 266 | } 267 | 268 | @Override 269 | public TreeSetView subSet(T fromElement, T toElement) { 270 | return subSet(fromElement, true, toElement, false); 271 | } 272 | 273 | @Override 274 | public TreeSetView headSet(T toElement) { 275 | return headSet(toElement, false); 276 | } 277 | 278 | @Override 279 | public TreeSetView tailSet(T fromElement) { 280 | return tailSet(fromElement, true); 281 | } 282 | 283 | @Override 284 | public T first() { 285 | return isEmpty() ? null : array[start]; 286 | } 287 | 288 | @Override 289 | public T last() { 290 | return isEmpty() ? null : array[end - 1]; 291 | } 292 | 293 | @Override 294 | public int size() { 295 | return end - start; 296 | } 297 | 298 | @Override 299 | public boolean isEmpty() { 300 | return size() == 0; 301 | } 302 | 303 | @Override 304 | public boolean contains(@Nullable Object o) { 305 | if (o == null) return false; 306 | if (size() == 0) return false; 307 | try { 308 | return Arrays.binarySearch(array, start, end, o) >= 0; 309 | } catch (ClassCastException ignored) { 310 | return false; 311 | } 312 | } 313 | 314 | @Override 315 | public T lower(T t) { 316 | var i = Arrays.binarySearch(array, start, end, t); 317 | if (i >= 0) { 318 | i = i - 1; 319 | } else { 320 | i = -i - 2; 321 | } 322 | return i >= start ? array[i] : null; 323 | } 324 | 325 | @Override 326 | public T floor(T t) { 327 | var i = Arrays.binarySearch(array, start, end, t); 328 | if (i >= 0) { 329 | return array[i]; 330 | } else { 331 | i = -i - 2; 332 | return i >= start ? array[i] : null; 333 | } 334 | } 335 | 336 | @Override 337 | public T ceiling(T t) { 338 | var i = Arrays.binarySearch(array, start, end, t); 339 | if (i >= 0) { 340 | return array[i]; 341 | } else { 342 | i = -i - 1; 343 | return i < end ? array[i] : null; 344 | } 345 | } 346 | 347 | @Override 348 | public T higher(T t) { 349 | var i = Arrays.binarySearch(array, start, end, t) + 1; 350 | i = i >= 0 ? i : -i; 351 | return i < end ? array[i] : null; 352 | } 353 | 354 | @Deprecated 355 | @Override 356 | public T pollFirst() { 357 | throw new UnsupportedOperationException("This is a read-only view."); 358 | } 359 | 360 | @Deprecated 361 | @Override 362 | public T pollLast() { 363 | throw new UnsupportedOperationException("This is a read-only view."); 364 | } 365 | 366 | @NonNull 367 | @Override 368 | public Iterator iterator() { 369 | return new Iterator<>() { 370 | int index = start; 371 | 372 | @Override 373 | public boolean hasNext() { 374 | return index < end; 375 | } 376 | 377 | @Override 378 | public T next() { 379 | return array[index++]; 380 | } 381 | }; 382 | } 383 | 384 | @Override 385 | public NavigableSet descendingSet() { 386 | throw new UnsupportedOperationException("Not implemented yet."); 387 | } 388 | 389 | @Override 390 | public Iterator descendingIterator() { 391 | return new Iterator<>() { 392 | int index = end - 1; 393 | 394 | @Override 395 | public boolean hasNext() { 396 | return index >= start; 397 | } 398 | 399 | @Override 400 | public T next() { 401 | return array[index--]; 402 | } 403 | }; 404 | } 405 | 406 | @Override 407 | public TreeSetView subSet(T fromElement, boolean fromInclusive, T toElement, boolean toInclusive) { 408 | int left = Arrays.binarySearch(array, start, end, fromElement); 409 | if (!fromInclusive && left >= 0) { 410 | left = left + 1; 411 | } else if (left < 0) { 412 | left = -left - 1; 413 | } 414 | int right = Arrays.binarySearch(array, start, end, toElement); 415 | if (toInclusive && right >= 0) { 416 | right = right + 1; 417 | } else if (right < 0) { 418 | right = -right - 1; 419 | } 420 | return new TreeSetView<>(array, left, Math.max(right, left)); 421 | } 422 | 423 | @Override 424 | public TreeSetView headSet(T toElement, boolean inclusive) { 425 | int right = Arrays.binarySearch(array, start, end, toElement); 426 | if (inclusive && right >= 0) { 427 | right = right + 1; 428 | } else if (right < 0) { 429 | right = -right - 1; 430 | } 431 | return new TreeSetView<>(array, start, right); 432 | } 433 | 434 | @Override 435 | public TreeSetView tailSet(T fromElement, boolean inclusive) { 436 | int left = Arrays.binarySearch(array, start, end, fromElement); 437 | if (!inclusive && left >= 0) { 438 | left = left + 1; 439 | } else if (left < 0) { 440 | left = -left - 1; 441 | } 442 | return new TreeSetView<>(array, left, array.length); 443 | } 444 | 445 | @NonNull 446 | @Override 447 | public Object[] toArray() { 448 | var arr = new Comparable[array.length]; 449 | System.arraycopy(array, start, arr, 0, size()); 450 | return arr; 451 | } 452 | 453 | @NonNull 454 | @Override 455 | public T1[] toArray(@NonNull T1[] a) { 456 | if (a.length >= array.length) { 457 | //noinspection SuspiciousSystemArraycopy 458 | System.arraycopy(array, start, a, 0, size()); 459 | return a; 460 | } else { 461 | //noinspection unchecked 462 | return (T1[]) toArray(); 463 | } 464 | } 465 | 466 | @Deprecated 467 | @Override 468 | public boolean add(T t) { 469 | throw new UnsupportedOperationException("This is a read-only view"); 470 | } 471 | 472 | @Deprecated 473 | @Override 474 | public boolean remove(@Nullable Object o) { 475 | throw new UnsupportedOperationException("This is a read-only view"); 476 | } 477 | 478 | @Override 479 | public boolean containsAll(@NonNull Collection c) { 480 | if (c instanceof Set && c.size() > size()) return false; 481 | if (c.isEmpty()) return true; 482 | if (isEmpty()) return false; 483 | if (c instanceof TreeSetView) { 484 | var other = (TreeSetView) c; 485 | var s = start; 486 | var p = other.start; 487 | while (p < other.end) { 488 | try { 489 | s = Arrays.binarySearch(array, s, end, other.array[p]); 490 | } catch (ClassCastException ignored) { 491 | return false; 492 | } 493 | if (s < 0) return false; 494 | p = p + 1; 495 | } 496 | } else { 497 | for (var o : c) { 498 | if (!contains(o)) return false; 499 | } 500 | } 501 | return true; 502 | } 503 | 504 | public boolean containsAny(@NonNull TreeSetView c) { 505 | if (isEmpty()) return false; 506 | if (c.isEmpty()) return true; 507 | int i = start, j = c.start; 508 | while (i < end && j < c.end) { 509 | var a = array[i]; 510 | var b = c.array[j]; 511 | int cmp = a.compareTo(b); 512 | if (cmp < 0) { 513 | i++; 514 | } else if (cmp > 0) { 515 | j++; 516 | } else { 517 | return true; 518 | } 519 | } 520 | return false; 521 | } 522 | 523 | @Override 524 | public boolean addAll(@NonNull Collection c) { 525 | throw new UnsupportedOperationException("This is a read-only view"); 526 | } 527 | 528 | @Override 529 | public boolean retainAll(@NonNull Collection c) { 530 | throw new UnsupportedOperationException("This is a read-only view"); 531 | } 532 | 533 | @Deprecated 534 | @Override 535 | public boolean removeAll(@NonNull Collection c) { 536 | throw new UnsupportedOperationException("This is a read-only view"); 537 | } 538 | 539 | @Deprecated 540 | @Override 541 | public void clear() { 542 | throw new UnsupportedOperationException("This is a read-only view"); 543 | } 544 | 545 | public TreeSetView merge(@NonNull TreeSetView other) { 546 | if (other.size() == 0) { 547 | return this; 548 | } else if (size() == 0) { 549 | return other; 550 | } 551 | // noinspection unchecked 552 | var res = (T[]) new Comparable[array.length + other.array.length]; 553 | int p = 0; 554 | int i = start, j = other.start; 555 | while (i < end && j < other.end) { 556 | var a = array[i]; 557 | var b = other.array[j]; 558 | int cmp = a.compareTo(b); 559 | if (cmp < 0) { 560 | res[p++] = a; 561 | i++; 562 | } else if (cmp > 0) { 563 | res[p++] = b; 564 | j++; 565 | } else { 566 | res[p++] = a; 567 | i++; 568 | j++; 569 | } 570 | } 571 | while (i < end) { 572 | res[p++] = array[i++]; 573 | } 574 | while (j < other.end) { 575 | res[p++] = other.array[j++]; 576 | } 577 | return new TreeSetView<>(res, 0, p); 578 | } 579 | } 580 | 581 | @SuppressWarnings("unused") 582 | final class IdTreeSetView { 583 | final private int[] array; 584 | // array[start, end); 585 | final private int start; 586 | final private int end; 587 | 588 | private IdTreeSetView(int[] array, int start, int end) { 589 | this.array = array; 590 | this.start = start; 591 | this.end = end; 592 | } 593 | 594 | static IdTreeSetView ofSorted(int[] array) { 595 | return new IdTreeSetView(array, 0, array.length); 596 | } 597 | 598 | static IdTreeSetView ofSorted(int[] array, int start, int end) { 599 | return new IdTreeSetView(array, start, end); 600 | } 601 | 602 | public IdTreeSetView subSet(int fromElement, int toElement) { 603 | return subSet(fromElement, true, toElement, false); 604 | } 605 | 606 | public IdTreeSetView headSet(int toElement) { 607 | return headSet(toElement, false); 608 | } 609 | 610 | public IdTreeSetView tailSet(int fromElement) { 611 | return tailSet(fromElement, true); 612 | } 613 | 614 | public int first() { 615 | return isEmpty() ? DexParser.NO_INDEX : array[start]; 616 | } 617 | 618 | public int last() { 619 | return isEmpty() ? DexParser.NO_INDEX : array[end - 1]; 620 | } 621 | 622 | public int size() { 623 | return end - start; 624 | } 625 | 626 | public boolean isEmpty() { 627 | return size() == 0; 628 | } 629 | 630 | public boolean contains(int o) { 631 | if (size() == 0) return false; 632 | return Arrays.binarySearch(array, start, end, o) >= 0; 633 | } 634 | 635 | public int lower(int t) { 636 | var i = Arrays.binarySearch(array, start, end, t); 637 | if (i >= 0) { 638 | i = i - 1; 639 | } else { 640 | i = -i - 2; 641 | } 642 | return i >= start ? array[i] : DexParser.NO_INDEX; 643 | } 644 | 645 | public int floor(int t) { 646 | var i = Arrays.binarySearch(array, start, end, t); 647 | if (i >= 0) { 648 | return array[i]; 649 | } else { 650 | i = -i - 2; 651 | return i >= start ? array[i] : DexParser.NO_INDEX; 652 | } 653 | } 654 | 655 | public int ceiling(int t) { 656 | var i = Arrays.binarySearch(array, start, end, t); 657 | if (i >= 0) { 658 | return array[i]; 659 | } else { 660 | i = -i - 1; 661 | return i < end ? array[i] : DexParser.NO_INDEX; 662 | } 663 | } 664 | 665 | public int higher(int t) { 666 | var i = Arrays.binarySearch(array, start, end, t) + 1; 667 | i = i >= 0 ? i : -i; 668 | return i < end ? array[i] : DexParser.NO_INDEX; 669 | } 670 | // 671 | // @NonNull 672 | // public Iterator iterator() { 673 | // return new Iterator<>() { 674 | // int index = start; 675 | // 676 | // @Override 677 | // public boolean hasNext() { 678 | // return index < end; 679 | // } 680 | // 681 | // @Override 682 | // public T next() { 683 | // return array[index++]; 684 | // } 685 | // }; 686 | // } 687 | 688 | // @Override 689 | // public Iterator descendingIterator() { 690 | // return new Iterator<>() { 691 | // int index = end - 1; 692 | // 693 | // @Override 694 | // public boolean hasNext() { 695 | // return index >= start; 696 | // } 697 | // 698 | // @Override 699 | // public T next() { 700 | // return array[index--]; 701 | // } 702 | // }; 703 | // } 704 | // 705 | public IdTreeSetView subSet(int fromElement, boolean fromInclusive, int toElement, boolean toInclusive) { 706 | int left = Arrays.binarySearch(array, start, end, fromElement); 707 | if (!fromInclusive && left >= 0) { 708 | left = left + 1; 709 | } else if (left < 0) { 710 | left = -left - 1; 711 | } 712 | int right = Arrays.binarySearch(array, start, end, toElement); 713 | if (toInclusive && right >= 0) { 714 | right = right + 1; 715 | } else if (right < 0) { 716 | right = -right - 1; 717 | } 718 | return new IdTreeSetView(array, left, Math.max(right, left)); 719 | } 720 | 721 | public IdTreeSetView headSet(int toElement, boolean inclusive) { 722 | int right = Arrays.binarySearch(array, start, end, toElement); 723 | if (inclusive && right >= 0) { 724 | right = right + 1; 725 | } else if (right < 0) { 726 | right = -right - 1; 727 | } 728 | return new IdTreeSetView(array, start, right); 729 | } 730 | 731 | public IdTreeSetView tailSet(int fromElement, boolean inclusive) { 732 | int left = Arrays.binarySearch(array, start, end, fromElement); 733 | if (!inclusive && left >= 0) { 734 | left = left + 1; 735 | } else if (left < 0) { 736 | left = -left - 1; 737 | } 738 | return new IdTreeSetView(array, left, array.length); 739 | } 740 | 741 | @NonNull 742 | public int[] toArray() { 743 | return array; 744 | } 745 | 746 | public boolean containsAll(@NonNull IdTreeSetView c) { 747 | if (c.isEmpty()) return true; 748 | if (isEmpty()) return false; 749 | var s = start; 750 | var p = c.start; 751 | while (p < c.end) { 752 | try { 753 | s = Arrays.binarySearch(array, s, end, c.array[p]); 754 | } catch (ClassCastException ignored) { 755 | return false; 756 | } 757 | if (s < 0) return false; 758 | p = p + 1; 759 | } 760 | return true; 761 | } 762 | 763 | public boolean containsAny(@NonNull IdTreeSetView c) { 764 | if (isEmpty()) return false; 765 | if (c.isEmpty()) return true; 766 | int i = start, j = c.start; 767 | while (i < end && j < c.end) { 768 | var a = array[i]; 769 | var b = c.array[j]; 770 | int cmp = a - b; 771 | if (cmp < 0) { 772 | i++; 773 | } else if (cmp > 0) { 774 | j++; 775 | } else { 776 | return true; 777 | } 778 | } 779 | return false; 780 | } 781 | 782 | public IdTreeSetView merge(@NonNull IdTreeSetView other) { 783 | if (other.size() == 0) { 784 | return this; 785 | } else if (size() == 0) { 786 | return other; 787 | } 788 | var res = new int[array.length + other.array.length]; 789 | int p = 0; 790 | int i = start, j = other.start; 791 | while (i < end && j < other.end) { 792 | var a = array[i]; 793 | var b = other.array[j]; 794 | int cmp = a - b; 795 | if (cmp < 0) { 796 | res[p++] = a; 797 | i++; 798 | } else if (cmp > 0) { 799 | res[p++] = b; 800 | j++; 801 | } else { 802 | res[p++] = a; 803 | i++; 804 | j++; 805 | } 806 | } 807 | while (i < end) { 808 | res[p++] = array[i++]; 809 | } 810 | while (j < other.end) { 811 | res[p++] = other.array[j++]; 812 | } 813 | return new IdTreeSetView(res, 0, p); 814 | } 815 | } 816 | 817 | class AtomicHelper { 818 | public static T updateIfNullAndGet(AtomicReference atomic, HookBuilder.Supplier updateFunction) { 819 | T next = null; 820 | while (true) { 821 | T now = atomic.get(); 822 | if (now != null) return now; 823 | if (next == null) next = updateFunction.get(); 824 | if (atomic.weakCompareAndSet(null, next)) 825 | return next; 826 | } 827 | } 828 | } 829 | -------------------------------------------------------------------------------- /helper-ktx/src/main/kotlin/io/github/libxposed/helper/ktx/HookBuilderKt.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package io.github.libxposed.helper.ktx 4 | 5 | import android.os.Build 6 | import android.os.Handler 7 | import androidx.annotation.RequiresApi 8 | import dalvik.system.BaseDexClassLoader 9 | import io.github.libxposed.api.XposedInterface 10 | import io.github.libxposed.helper.HookBuilder 11 | import io.github.libxposed.helper.HookBuilder.* 12 | import java.io.InputStream 13 | import java.io.OutputStream 14 | import java.lang.reflect.Constructor 15 | import java.lang.reflect.Field 16 | import java.lang.reflect.Member 17 | import java.lang.reflect.Method 18 | import java.util.concurrent.ExecutorService 19 | 20 | 21 | @DslMarker 22 | internal annotation class Hooker 23 | 24 | @DslMarker 25 | internal annotation class Matcher 26 | 27 | @PublishedApi 28 | internal val wo: Nothing 29 | get() = throw UnsupportedOperationException("Write-only property") 30 | 31 | @Matcher 32 | @Hooker 33 | object DummyHooker 34 | 35 | 36 | @RequiresOptIn(message = "Dex analysis is time-consuming, please use it carefully.") 37 | @Retention(AnnotationRetention.BINARY) 38 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) 39 | annotation class DexAnalysis 40 | 41 | @RequiresOptIn(message = "Annotation analysis is time-consuming, please use it carefully.") 42 | @Retention(AnnotationRetention.BINARY) 43 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) 44 | annotation class AnnotationAnalysis 45 | 46 | @Matcher 47 | @Hooker 48 | abstract class LazyBind { 49 | @PublishedApi 50 | internal val bind = object : HookBuilder.LazyBind { 51 | override fun onMatch() = this@LazyBind.onMatch() 52 | 53 | override fun onMiss() = this@LazyBind.onMiss() 54 | } 55 | 56 | abstract fun onMatch() 57 | 58 | abstract fun onMiss() 59 | } 60 | 61 | class ParameterKt @PublishedApi internal constructor(@PublishedApi internal val parameter: Parameter) { 62 | val type: Class<*> 63 | inline get() = parameter.type 64 | 65 | val declaringExecutable: Member 66 | inline get() = parameter.declaringExecutable 67 | 68 | val index: Int 69 | inline get() = parameter.index 70 | } 71 | 72 | class SyntaxKt @PublishedApi internal constructor(@PublishedApi internal val syntax: Syntax) where MatchKt : BaseMatchKt, Match : BaseMatch { 73 | infix fun and(element: SyntaxKt) = 74 | SyntaxKt(syntax.and(element.syntax)) 75 | 76 | infix fun or(element: SyntaxKt) = 77 | SyntaxKt(syntax.or(element.syntax)) 78 | 79 | operator fun not() = SyntaxKt(syntax.not()) 80 | } 81 | 82 | @Matcher 83 | sealed class ReflectMatcherKt(@PublishedApi internal val matcher: Matcher) where Matcher : ReflectMatcher { 84 | var key: String 85 | @Deprecated( 86 | "Write only", level = DeprecationLevel.HIDDEN 87 | ) inline get() = wo 88 | inline set(value) { 89 | matcher.setKey(value) 90 | } 91 | var isPublic: Boolean 92 | @Deprecated( 93 | "Write only", level = DeprecationLevel.HIDDEN 94 | ) inline get() = wo 95 | inline set(value) { 96 | matcher.setIsPublic(value) 97 | } 98 | var isPrivate: Boolean 99 | @Deprecated( 100 | "Write only", level = DeprecationLevel.HIDDEN 101 | ) inline get() = wo 102 | inline set(value) { 103 | matcher.setIsPrivate(value) 104 | } 105 | var isProtected: Boolean 106 | @Deprecated( 107 | "Write only", level = DeprecationLevel.HIDDEN 108 | ) inline get() = wo 109 | set(value) { 110 | matcher.setIsProtected(value) 111 | } 112 | var isPackage: Boolean 113 | @Deprecated( 114 | "Write only", level = DeprecationLevel.HIDDEN 115 | ) inline get() = wo 116 | set(value) { 117 | matcher.setIsPackage(value) 118 | } 119 | } 120 | 121 | class ClassMatcherKt @PublishedApi internal constructor(matcher: ClassMatcher) : 122 | ReflectMatcherKt(matcher) { 123 | var name: StringMatchKt 124 | @Deprecated( 125 | "Write only", level = DeprecationLevel.HIDDEN 126 | ) inline get() = wo 127 | inline set(value) { 128 | matcher.setName(value.match) 129 | } 130 | var superClass: ClassMatchKt 131 | @Deprecated( 132 | "Write only", level = DeprecationLevel.HIDDEN 133 | ) inline get() = wo 134 | inline set(value) { 135 | matcher.setSuperClass(value.match) 136 | } 137 | var containsInterfaces: SyntaxKt 138 | @Deprecated( 139 | "Write only", level = DeprecationLevel.HIDDEN 140 | ) inline get() = wo 141 | inline set(value) { 142 | matcher.setContainsInterfaces(value.syntax) 143 | } 144 | var isAbstract: Boolean 145 | @Deprecated( 146 | "Write only", level = DeprecationLevel.HIDDEN 147 | ) inline get() = wo 148 | inline set(value) { 149 | matcher.setIsAbstract(value) 150 | } 151 | var isStatic: Boolean 152 | @Deprecated( 153 | "Write only", level = DeprecationLevel.HIDDEN 154 | ) inline get() = wo 155 | inline set(value) { 156 | matcher.setIsStatic(value) 157 | } 158 | var isFinal: Boolean 159 | @Deprecated( 160 | "Write only", level = DeprecationLevel.HIDDEN 161 | ) inline get() = wo 162 | inline set(value) { 163 | matcher.setIsFinal(value) 164 | } 165 | var isInterface: Boolean 166 | @Deprecated( 167 | "Write only", level = DeprecationLevel.HIDDEN 168 | ) inline get() = wo 169 | inline set(value) { 170 | matcher.setIsInterface(value) 171 | } 172 | } 173 | 174 | class ParameterMatcherKt @PublishedApi internal constructor(matcher: ParameterMatcher) : 175 | ReflectMatcherKt(matcher) { 176 | var index: Int 177 | @Deprecated( 178 | "Write only", level = DeprecationLevel.HIDDEN 179 | ) inline get() = wo 180 | inline set(value) { 181 | matcher.setIndex(value) 182 | } 183 | 184 | var type: ClassMatchKt 185 | @Deprecated( 186 | "Write only", level = DeprecationLevel.HIDDEN 187 | ) inline get() = wo 188 | inline set(value) { 189 | matcher.setType(value.match) 190 | } 191 | 192 | var isFinal: Boolean 193 | @Deprecated( 194 | "Write only", level = DeprecationLevel.HIDDEN 195 | ) inline get() = wo 196 | @RequiresApi(Build.VERSION_CODES.O) inline set(value) { 197 | matcher.setIsFinal(value) 198 | } 199 | 200 | var isSynthetic: Boolean 201 | @Deprecated( 202 | "Write only", level = DeprecationLevel.HIDDEN 203 | ) inline get() = wo 204 | @RequiresApi(Build.VERSION_CODES.O) inline set(value) { 205 | matcher.setIsSynthetic(value) 206 | } 207 | 208 | var isImplicit: Boolean 209 | @Deprecated( 210 | "Write only", level = DeprecationLevel.HIDDEN 211 | ) inline get() = wo 212 | @RequiresApi(Build.VERSION_CODES.O) inline set(value) { 213 | matcher.setIsImplicit(value) 214 | } 215 | 216 | var isVarargs: Boolean 217 | @Deprecated( 218 | "Write only", level = DeprecationLevel.HIDDEN 219 | ) inline get() = wo 220 | @RequiresApi(Build.VERSION_CODES.O) inline set(value) { 221 | matcher.setIsVarargs(value) 222 | } 223 | } 224 | 225 | sealed class MemberMatcherKt(matcher: Matcher) : 226 | ReflectMatcherKt(matcher) where Matcher : MemberMatcher { 227 | var declaringClass: ClassMatchKt 228 | @Deprecated( 229 | "Write only", level = DeprecationLevel.HIDDEN 230 | ) inline get() = wo 231 | inline set(value) { 232 | matcher.setDeclaringClass(value.match) 233 | } 234 | var isSynthetic: Boolean 235 | @Deprecated( 236 | "Write only", level = DeprecationLevel.HIDDEN 237 | ) inline get() = wo 238 | inline set(value) { 239 | matcher.setIsSynthetic(value) 240 | } 241 | var includeSuper: Boolean 242 | @Deprecated( 243 | "Write only", level = DeprecationLevel.HIDDEN 244 | ) inline get() = wo 245 | inline set(value) { 246 | matcher.setIncludeSuper(value) 247 | } 248 | var includeInterface: Boolean 249 | @Deprecated( 250 | "Write only", level = DeprecationLevel.HIDDEN 251 | ) inline get() = wo 252 | inline set(value) { 253 | matcher.setIncludeInterface(value) 254 | } 255 | } 256 | 257 | class FieldMatcherKt @PublishedApi internal constructor(matcher: FieldMatcher) : 258 | MemberMatcherKt(matcher) { 259 | var name: StringMatchKt 260 | @Deprecated( 261 | "Write only", level = DeprecationLevel.HIDDEN 262 | ) inline get() = wo 263 | inline set(value) { 264 | matcher.setName(value.match) 265 | } 266 | var type: ClassMatchKt 267 | @Deprecated( 268 | "Write only", level = DeprecationLevel.HIDDEN 269 | ) inline get() = wo 270 | inline set(value) { 271 | matcher.setType(value.match) 272 | } 273 | var isStatic: Boolean 274 | @Deprecated( 275 | "Write only", level = DeprecationLevel.HIDDEN 276 | ) inline get() = wo 277 | inline set(value) { 278 | matcher.setIsStatic(value) 279 | } 280 | var isFinal: Boolean 281 | @Deprecated( 282 | "Write only", level = DeprecationLevel.HIDDEN 283 | ) inline get() = wo 284 | inline set(value) { 285 | matcher.setIsFinal(value) 286 | } 287 | var isTransient: Boolean 288 | @Deprecated( 289 | "Write only", level = DeprecationLevel.HIDDEN 290 | ) inline get() = wo 291 | inline set(value) { 292 | matcher.setIsTransient(value) 293 | } 294 | var isVolatile: Boolean 295 | @Deprecated( 296 | "Write only", level = DeprecationLevel.HIDDEN 297 | ) inline get() = wo 298 | inline set(value) { 299 | matcher.setIsVolatile(value) 300 | } 301 | } 302 | 303 | sealed class ExecutableMatcherKt(matcher: Matcher) : 304 | MemberMatcherKt(matcher) where Matcher : ExecutableMatcher { 305 | var parameterCounts: Int 306 | @Deprecated( 307 | "Write only", level = DeprecationLevel.HIDDEN 308 | ) inline get() = wo 309 | inline set(value) { 310 | matcher.setParameterCount(value) 311 | } 312 | 313 | var parameters: SyntaxKt 314 | @Deprecated( 315 | "Write only", level = DeprecationLevel.HIDDEN 316 | ) inline get() = wo 317 | inline set(value) { 318 | matcher.setParameters(value.syntax) 319 | } 320 | 321 | @DexAnalysis 322 | var referredStrings: SyntaxKt 323 | @Deprecated( 324 | "Write only", level = DeprecationLevel.HIDDEN 325 | ) inline get() = wo 326 | inline set(value) { 327 | matcher.setReferredStrings(value.syntax) 328 | } 329 | 330 | @DexAnalysis 331 | var assignedFields: SyntaxKt 332 | @Deprecated( 333 | "Write only", level = DeprecationLevel.HIDDEN 334 | ) inline get() = wo 335 | inline set(value) { 336 | matcher.setAssignedFields(value.syntax) 337 | } 338 | 339 | @DexAnalysis 340 | var accessedFields: SyntaxKt 341 | @Deprecated( 342 | "Write only", level = DeprecationLevel.HIDDEN 343 | ) inline get() = wo 344 | inline set(value) { 345 | matcher.setAccessedFields(value.syntax) 346 | } 347 | 348 | @DexAnalysis 349 | var invokedMethods: SyntaxKt 350 | @Deprecated( 351 | "Write only", level = DeprecationLevel.HIDDEN 352 | ) inline get() = wo 353 | inline set(value) { 354 | matcher.setInvokedMethods(value.syntax) 355 | } 356 | 357 | @DexAnalysis 358 | var invokedConstructor: SyntaxKt 359 | @Deprecated( 360 | "Write only", level = DeprecationLevel.HIDDEN 361 | ) inline get() = wo 362 | inline set(value) { 363 | matcher.setInvokedConstructors(value.syntax) 364 | } 365 | 366 | @DexAnalysis 367 | var containsOpcodes: ByteArray 368 | @Deprecated( 369 | "Write only", level = DeprecationLevel.HIDDEN 370 | ) inline get() = wo 371 | inline set(value) { 372 | matcher.setContainsOpcodes(value) 373 | } 374 | 375 | var isVarargs: Boolean 376 | @Deprecated( 377 | "Write only", level = DeprecationLevel.HIDDEN 378 | ) inline get() = wo 379 | inline set(value) { 380 | matcher.setIsVarargs(value) 381 | } 382 | 383 | fun conjunction(vararg types: Class<*>?) = 384 | SyntaxKt(matcher.conjunction(*types)) 385 | 386 | fun conjunction(vararg types: ClassMatchKt?) = 387 | SyntaxKt(matcher.conjunction(*types.map { it?.match } 388 | .toTypedArray())) 389 | 390 | inline fun firstParameter(crossinline init: ParameterMatcherKt.() -> Unit) = 391 | ParameterMatchKt(matcher.firstParameter { 392 | ParameterMatcherKt(it).init() 393 | }) 394 | 395 | inline fun parameters(crossinline init: ParameterMatcherKt.() -> Unit) = 396 | ParameterLazySequenceKt(matcher.parameters { 397 | ParameterMatcherKt(it).init() 398 | }) 399 | 400 | operator fun Class<*>.get(index: Int) = 401 | SyntaxKt(matcher.observe(index, this)) 402 | 403 | operator fun ClassMatchKt.get(index: Int) = 404 | SyntaxKt(matcher.observe(index, this.match)) 405 | } 406 | 407 | class MethodMatcherKt @PublishedApi internal constructor(matcher: MethodMatcher) : 408 | ExecutableMatcherKt(matcher) { 409 | var name: StringMatchKt 410 | @Deprecated( 411 | "Write only", level = DeprecationLevel.HIDDEN 412 | ) inline get() = wo 413 | inline set(value) { 414 | matcher.setName(value.match) 415 | } 416 | var returnType: ClassMatchKt 417 | @Deprecated( 418 | "Write only", level = DeprecationLevel.HIDDEN 419 | ) inline get() = wo 420 | inline set(value) { 421 | matcher.setReturnType(value.match) 422 | } 423 | var isAbstract: Boolean 424 | @Deprecated( 425 | "Write only", level = DeprecationLevel.HIDDEN 426 | ) inline get() = wo 427 | inline set(value) { 428 | matcher.setIsAbstract(value) 429 | } 430 | var isStatic: Boolean 431 | @Deprecated( 432 | "Write only", level = DeprecationLevel.HIDDEN 433 | ) inline get() = wo 434 | inline set(value) { 435 | matcher.setIsStatic(value) 436 | } 437 | var isFinal: Boolean 438 | @Deprecated( 439 | "Write only", level = DeprecationLevel.HIDDEN 440 | ) inline get() = wo 441 | inline set(value) { 442 | matcher.setIsFinal(value) 443 | } 444 | var isSynchronized: Boolean 445 | @Deprecated( 446 | "Write only", level = DeprecationLevel.HIDDEN 447 | ) inline get() = wo 448 | inline set(value) { 449 | matcher.setIsSynchronized(value) 450 | } 451 | var isNative: Boolean 452 | @Deprecated( 453 | "Write only", level = DeprecationLevel.HIDDEN 454 | ) inline get() = wo 455 | inline set(value) { 456 | matcher.setIsNative(value) 457 | } 458 | } 459 | 460 | class ConstructorMatcherKt @PublishedApi internal constructor(matcher: ConstructorMatcher) : 461 | ExecutableMatcherKt(matcher) 462 | 463 | @Hooker 464 | sealed class BaseMatchKt( 465 | @PublishedApi internal val match: Match 466 | ) where Self : BaseMatchKt, Match : BaseMatch { 467 | operator fun unaryPlus() = SyntaxKt(match.observe()) 468 | 469 | operator fun unaryMinus() = SyntaxKt(match.reverse()) 470 | } 471 | 472 | @Suppress("UNCHECKED_CAST") 473 | sealed class ReflectMatchKt(match: Match) : 474 | BaseMatchKt(match) where Self : ReflectMatchKt, Match : ReflectMatch, Matcher : ReflectMatcher, MatcherKt : ReflectMatcherKt { 475 | var key: String? 476 | inline get() = match.key 477 | inline set(value) { 478 | match.key = value 479 | } 480 | 481 | inline fun onMiss(crossinline handler: DummyHooker.() -> Unit): Self { 482 | match.onMiss { 483 | DummyHooker.handler() 484 | } 485 | return this as Self 486 | } 487 | 488 | inline fun substituteIfMiss(crossinline substitute: () -> Self): Self { 489 | match.substituteIfMiss { 490 | substitute().match 491 | } 492 | return this as Self 493 | } 494 | 495 | inline fun matchFirstIfMiss(crossinline handler: MatcherKt.() -> Unit): Self { 496 | match.matchFirstIfMiss { 497 | newMatcher(it).handler() 498 | } 499 | return this as Self 500 | } 501 | 502 | @PublishedApi 503 | internal abstract fun newSelf(match: Match): Self 504 | 505 | @PublishedApi 506 | internal abstract fun newMatcher(match: Matcher): MatcherKt 507 | } 508 | 509 | class ClassMatchKt @PublishedApi internal constructor(match: ClassMatch) : 510 | ReflectMatchKt, ClassMatcher, ClassMatcherKt>(match) { 511 | val superClass: ClassMatchKt 512 | inline get() = ClassMatchKt(match.superClass) 513 | val interfaces: ClassLazySequenceKt 514 | inline get() = ClassLazySequenceKt(match.interfaces) 515 | val declaredMethods: MethodLazySequenceKt 516 | inline get() = MethodLazySequenceKt(match.declaredMethods) 517 | val declaredConstructors: ConstructorLazySequenceKt 518 | inline get() = ConstructorLazySequenceKt(match.declaredConstructors) 519 | val declaredFields: FieldLazySequenceKt 520 | inline get() = FieldLazySequenceKt(match.declaredFields) 521 | val arrayType: ClassMatchKt 522 | inline get() = ClassMatchKt(match.arrayType) 523 | 524 | override fun newSelf(match: ClassMatch) = ClassMatchKt(match) 525 | 526 | override fun newMatcher(match: ClassMatcher) = ClassMatcherKt(match) 527 | 528 | inline fun onMatch(crossinline handler: DummyHooker.(Class<*>) -> Unit): ClassMatchKt { 529 | match.onMatch { 530 | DummyHooker.handler(it) 531 | } 532 | return this 533 | } 534 | 535 | inline fun bind( 536 | bind: Bind, crossinline handler: Bind.(Class<*>) -> Unit 537 | ): ClassMatchKt { 538 | match.bind(bind.bind) { _, r -> 539 | bind.handler(r) 540 | } 541 | return this 542 | } 543 | } 544 | 545 | class ParameterMatchKt @PublishedApi internal constructor(match: ParameterMatch) : 546 | ReflectMatchKt( 547 | match 548 | ) { 549 | val type: ClassMatchKt 550 | inline get() = ClassMatchKt(match.type) 551 | 552 | override fun newSelf(match: ParameterMatch) = ParameterMatchKt(match) 553 | override fun newMatcher(match: ParameterMatcher) = ParameterMatcherKt(match) 554 | 555 | inline fun onMatch(crossinline handler: DummyHooker.(ParameterKt) -> Unit): ParameterMatchKt { 556 | match.onMatch { 557 | DummyHooker.handler(ParameterKt(it)) 558 | } 559 | return this 560 | } 561 | 562 | inline fun bind( 563 | bind: Bind, crossinline handler: Bind.(ParameterKt) -> Unit 564 | ): ParameterMatchKt { 565 | match.bind(bind.bind) { _, r -> 566 | bind.handler(ParameterKt(r)) 567 | } 568 | return this 569 | } 570 | } 571 | 572 | sealed class MemberMatchKt(match: Match) : 573 | ReflectMatchKt(match) where Self : MemberMatchKt, Match : MemberMatch, Reflect : Member, Matcher : MemberMatcher, MatcherKt : MemberMatcherKt { 574 | val declaringClass: ClassMatchKt 575 | inline get() = ClassMatchKt(match.declaringClass) 576 | 577 | @Suppress("UNCHECKED_CAST") 578 | inline fun onMatch(crossinline handler: DummyHooker.(Reflect) -> Unit): Self { 579 | match.onMatch { 580 | DummyHooker.handler(it) 581 | } 582 | return this as Self 583 | } 584 | 585 | @Suppress("UNCHECKED_CAST") 586 | inline fun bind( 587 | bind: Bind, crossinline handler: Bind.(Reflect) -> Unit 588 | ): Self { 589 | match.bind(bind.bind) { _, r -> 590 | bind.handler(r) 591 | } 592 | return this as Self 593 | } 594 | } 595 | 596 | sealed class ExecutableMatchKt(match: Match) : 597 | MemberMatchKt(match) where Self : ExecutableMatchKt, Match : ExecutableMatch, Reflect : Member, Matcher : ExecutableMatcher, MatcherKt : ExecutableMatcherKt { 598 | val parameterTypes: ClassLazySequenceKt 599 | inline get() = ClassLazySequenceKt(match.parameterTypes) 600 | 601 | val parameters: ParameterLazySequenceKt 602 | inline get() = ParameterLazySequenceKt(match.parameters) 603 | 604 | @DexAnalysis 605 | val assignedFields: FieldLazySequenceKt 606 | inline get() = FieldLazySequenceKt(match.assignedFields) 607 | 608 | @DexAnalysis 609 | val accessedFields: FieldLazySequenceKt 610 | inline get() = FieldLazySequenceKt(match.accessedFields) 611 | 612 | @DexAnalysis 613 | val invokedMethods: MethodLazySequenceKt 614 | inline get() = MethodLazySequenceKt(match.invokedMethods) 615 | 616 | @DexAnalysis 617 | val invokedConstructors: ConstructorLazySequenceKt 618 | inline get() = ConstructorLazySequenceKt(match.invokedConstructors) 619 | } 620 | 621 | class MethodMatchKt @PublishedApi internal constructor(match: MethodMatch) : 622 | ExecutableMatchKt(match) { 623 | val returnType: ClassMatchKt 624 | inline get() = ClassMatchKt(match.returnType) 625 | 626 | override fun newSelf(match: MethodMatch) = MethodMatchKt(match) 627 | override fun newMatcher(match: MethodMatcher) = MethodMatcherKt(match) 628 | } 629 | 630 | class ConstructorMatchKt @PublishedApi internal constructor(match: ConstructorMatch) : 631 | ExecutableMatchKt, ConstructorMatcher, ConstructorMatcherKt>( 632 | match 633 | ) { 634 | override fun newSelf(match: ConstructorMatch) = ConstructorMatchKt(match) 635 | override fun newMatcher(match: ConstructorMatcher) = ConstructorMatcherKt(match) 636 | } 637 | 638 | class FieldMatchKt @PublishedApi internal constructor(match: FieldMatch) : 639 | MemberMatchKt(match) { 640 | val type: ClassMatchKt 641 | inline get() = ClassMatchKt(match.type) 642 | 643 | override fun newSelf(match: FieldMatch) = FieldMatchKt(match) 644 | override fun newMatcher(match: FieldMatcher) = FieldMatcherKt(match) 645 | } 646 | 647 | class StringMatchKt @PublishedApi internal constructor(match: StringMatch) : 648 | BaseMatchKt(match) 649 | 650 | @Suppress("UNCHECKED_CAST") 651 | @Hooker 652 | sealed class LazySequenceKt( 653 | @PublishedApi internal val seq: Seq 654 | ) where Self : LazySequenceKt, MatchKt : ReflectMatchKt, Reflect : Any, MatcherKt : ReflectMatcherKt, Match : ReflectMatch, Matcher : ReflectMatcher, Seq : LazySequence { 655 | fun first() = newMatch(seq.first()) 656 | fun unaryPlus() = SyntaxKt(seq.conjunction()) 657 | 658 | fun unaryMinus() = SyntaxKt(seq.disjunction()) 659 | 660 | inline fun all(crossinline init: MatcherKt.() -> Unit) = newSelf(seq.all { 661 | newMatcher(it).init() 662 | }) 663 | 664 | inline fun first(crossinline init: MatcherKt.() -> Unit) = newMatch(seq.first { 665 | newMatcher(it).init() 666 | }) 667 | 668 | inline fun substituteIfMiss(crossinline substitute: () -> Self): Self { 669 | seq.substituteIfMiss { 670 | substitute().seq 671 | } 672 | return this as Self 673 | } 674 | 675 | inline fun matchIfMiss(crossinline handler: MatcherKt.() -> Unit): Self { 676 | seq.matchIfMiss { 677 | newMatcher(it).handler() 678 | } 679 | return this as Self 680 | } 681 | 682 | inline fun onMiss(crossinline handler: DummyHooker.() -> Unit): Self { 683 | seq.onMiss { 684 | DummyHooker.handler() 685 | } 686 | return this as Self 687 | } 688 | 689 | @PublishedApi 690 | internal abstract fun newMatch(impl: Match): MatchKt 691 | 692 | @PublishedApi 693 | internal abstract fun newMatcher(impl: Matcher): MatcherKt 694 | 695 | @PublishedApi 696 | internal abstract fun newSelf(impl: Seq): Self 697 | } 698 | 699 | class ClassLazySequenceKt @PublishedApi internal constructor(seq: ClassLazySequence) : 700 | LazySequenceKt, ClassMatcherKt, ClassMatch, ClassMatcher, ClassLazySequence>( 701 | seq 702 | ) { 703 | inline fun methods(crossinline init: MethodMatcherKt.() -> Unit) = 704 | MethodLazySequenceKt(seq.methods { 705 | MethodMatcherKt(it).init() 706 | }) 707 | 708 | inline fun firstMethod(crossinline init: MethodMatcherKt.() -> Unit) = 709 | MethodMatchKt(seq.firstMethod { 710 | MethodMatcherKt(it).init() 711 | }) 712 | 713 | inline fun fields(crossinline init: FieldMatcherKt.() -> Unit) = 714 | FieldLazySequenceKt(seq.fields { 715 | FieldMatcherKt(it).init() 716 | }) 717 | 718 | inline fun firstField(crossinline init: FieldMatcherKt.() -> Unit) = 719 | FieldMatchKt(seq.firstField { 720 | FieldMatcherKt(it).init() 721 | }) 722 | 723 | inline fun constructors(crossinline init: ConstructorMatcherKt.() -> Unit) = 724 | ConstructorLazySequenceKt(seq.constructors { 725 | ConstructorMatcherKt(it).init() 726 | }) 727 | 728 | inline fun firstConstructor(crossinline init: ConstructorMatcherKt.() -> Unit) = 729 | ConstructorMatchKt(seq.firstConstructor { 730 | ConstructorMatcherKt(it).init() 731 | }) 732 | 733 | override fun newMatch(impl: ClassMatch) = ClassMatchKt(impl) 734 | 735 | override fun newMatcher(impl: ClassMatcher) = ClassMatcherKt(impl) 736 | 737 | override fun newSelf(impl: ClassLazySequence) = ClassLazySequenceKt(impl) 738 | 739 | inline fun bind( 740 | bind: Bind, crossinline handler: Bind.(Sequence>) -> Unit 741 | ): ClassLazySequenceKt { 742 | seq.bind(bind.bind) { _, r -> 743 | bind.handler(r.asSequence()) 744 | } 745 | return this 746 | } 747 | 748 | inline fun onMatch(crossinline handler: DummyHooker.(Sequence>) -> Unit): ClassLazySequenceKt { 749 | seq.onMatch { 750 | DummyHooker.handler(it.asSequence()) 751 | } 752 | return this 753 | } 754 | } 755 | 756 | sealed class MemberLazySequenceKt(seq: Seq) : 757 | LazySequenceKt(seq) where Self : MemberLazySequenceKt, MatchKt : MemberMatchKt, Reflect : Member, MatcherKt : MemberMatcherKt, Match : MemberMatch, Matcher : MemberMatcher, Seq : MemberLazySequence { 758 | inline fun declaringClasses(crossinline init: ClassMatcherKt.() -> Unit) = 759 | ClassLazySequenceKt(seq.declaringClasses { 760 | ClassMatcherKt(it).init() 761 | }) 762 | 763 | inline fun firstDeclaringClass(crossinline init: ClassMatcherKt.() -> Unit) = 764 | ClassMatchKt(seq.firstDeclaringClass { 765 | ClassMatcherKt(it).init() 766 | }) 767 | 768 | @Suppress("UNCHECKED_CAST") 769 | inline fun bind( 770 | bind: Bind, crossinline handler: Bind.(Sequence) -> Unit 771 | ): Self { 772 | seq.bind(bind.bind) { _, r -> 773 | bind.handler(r.asSequence()) 774 | } 775 | return this as Self 776 | } 777 | 778 | @Suppress("UNCHECKED_CAST") 779 | inline fun onMatch(crossinline handler: DummyHooker.(Sequence) -> Unit): Self { 780 | seq.onMatch { 781 | DummyHooker.handler(it.asSequence()) 782 | } 783 | return this as Self 784 | } 785 | } 786 | 787 | sealed class ExecutableLazySequenceKt(seq: Seq) : 788 | MemberLazySequenceKt(seq) where Self : ExecutableLazySequenceKt, MatchKt : ExecutableMatchKt, Reflect : Member, MatcherKt : ExecutableMatcherKt, Match : ExecutableMatch, Matcher : ExecutableMatcher, Seq : ExecutableLazySequence { 789 | inline fun parameters(crossinline init: ParameterMatcherKt.() -> Unit) = 790 | ParameterLazySequenceKt(seq.parameters { 791 | ParameterMatcherKt(it).init() 792 | }) 793 | 794 | inline fun firstParameter(crossinline init: ParameterMatcherKt.() -> Unit) = 795 | ParameterMatchKt(seq.firstParameter { 796 | ParameterMatcherKt(it).init() 797 | }) 798 | } 799 | 800 | class ParameterLazySequenceKt @PublishedApi internal constructor(impl: ParameterLazySequence) : 801 | LazySequenceKt( 802 | impl 803 | ) { 804 | inline fun types(crossinline init: ClassMatcherKt.() -> Unit) = ClassLazySequenceKt(seq.types { 805 | ClassMatcherKt(it).init() 806 | }) 807 | 808 | inline fun firstType(crossinline init: ClassMatcherKt.() -> Unit) = ClassMatchKt(seq.firstType { 809 | ClassMatcherKt(it).init() 810 | }) 811 | 812 | override fun newMatch(impl: ParameterMatch) = ParameterMatchKt(impl) 813 | 814 | override fun newMatcher(impl: ParameterMatcher) = ParameterMatcherKt(impl) 815 | 816 | override fun newSelf(impl: ParameterLazySequence) = ParameterLazySequenceKt(impl) 817 | 818 | @Suppress("UNCHECKED_CAST") 819 | inline fun bind( 820 | bind: Bind, crossinline handler: Bind.(Sequence) -> Unit 821 | ): ParameterLazySequenceKt { 822 | seq.bind(bind.bind) { _, r -> 823 | bind.handler(r.asSequence().map { ParameterKt(it) }) 824 | } 825 | return this 826 | } 827 | 828 | inline fun onMatch(crossinline handler: DummyHooker.(Sequence) -> Unit): ParameterLazySequenceKt { 829 | seq.onMatch { r -> 830 | DummyHooker.handler(r.asSequence().map { ParameterKt(it) }) 831 | } 832 | return this 833 | } 834 | } 835 | 836 | class FieldLazySequenceKt @PublishedApi internal constructor(impl: FieldLazySequence) : 837 | MemberLazySequenceKt( 838 | impl 839 | ) { 840 | override fun newMatch(impl: FieldMatch) = FieldMatchKt(impl) 841 | 842 | override fun newMatcher(impl: FieldMatcher) = FieldMatcherKt(impl) 843 | 844 | override fun newSelf(impl: FieldLazySequence) = FieldLazySequenceKt(impl) 845 | inline fun types(crossinline init: ClassMatcherKt.() -> Unit) = ClassLazySequenceKt(seq.types { 846 | ClassMatcherKt(it).init() 847 | }) 848 | 849 | inline fun firstType(crossinline init: ClassMatcherKt.() -> Unit) = ClassMatchKt(seq.firstType { 850 | ClassMatcherKt(it).init() 851 | }) 852 | } 853 | 854 | class MethodLazySequenceKt @PublishedApi internal constructor(impl: MethodLazySequence) : 855 | ExecutableLazySequenceKt( 856 | impl 857 | ) { 858 | override fun newMatch(impl: MethodMatch) = MethodMatchKt(impl) 859 | 860 | override fun newMatcher(impl: MethodMatcher) = MethodMatcherKt(impl) 861 | 862 | override fun newSelf(impl: MethodLazySequence) = MethodLazySequenceKt(impl) 863 | inline fun returnTypes(crossinline init: ClassMatcherKt.() -> Unit) = 864 | ClassLazySequenceKt(seq.returnTypes { 865 | ClassMatcherKt(it).init() 866 | }) 867 | 868 | inline fun firstReturnType(crossinline init: ClassMatcherKt.() -> Unit) = 869 | ClassMatchKt(seq.firstReturnType { 870 | ClassMatcherKt(it).init() 871 | }) 872 | } 873 | 874 | class ConstructorLazySequenceKt @PublishedApi internal constructor(seq: ConstructorLazySequence) : 875 | ExecutableLazySequenceKt, ConstructorMatcherKt, ConstructorMatch, ConstructorMatcher, ConstructorLazySequence>( 876 | seq 877 | ) { 878 | override fun newMatch(impl: ConstructorMatch) = ConstructorMatchKt(impl) 879 | 880 | override fun newMatcher(impl: ConstructorMatcher) = ConstructorMatcherKt(impl) 881 | 882 | override fun newSelf(impl: ConstructorLazySequence) = ConstructorLazySequenceKt(impl) 883 | } 884 | 885 | @Hooker 886 | class HookBuilderKt(@PublishedApi internal val builder: HookBuilder) { 887 | var exceptionHandler: (Throwable) -> Boolean 888 | @Deprecated( 889 | "Write only", level = DeprecationLevel.HIDDEN 890 | ) inline get() = wo 891 | inline set(crossinline value) { 892 | builder.setExceptionHandler { 893 | value(it) 894 | } 895 | } 896 | 897 | @DexAnalysis 898 | var forceDexAnalysis: Boolean 899 | @Deprecated( 900 | "Write only", level = DeprecationLevel.HIDDEN 901 | ) inline get() = wo 902 | inline set(value) { 903 | builder.setForceDexAnalysis(value) 904 | } 905 | 906 | var executorService: ExecutorService 907 | @Deprecated( 908 | "Write only", level = DeprecationLevel.HIDDEN 909 | ) inline get() = wo 910 | inline set(value) { 911 | builder.setExecutorService(value) 912 | } 913 | 914 | var callbackHandler: Handler 915 | @Deprecated( 916 | "Write only", level = DeprecationLevel.HIDDEN 917 | ) inline get() = wo 918 | inline set(value) { 919 | builder.setCallbackHandler(value) 920 | } 921 | 922 | var cacheInputStream: InputStream 923 | @Deprecated( 924 | "Write only", level = DeprecationLevel.HIDDEN 925 | ) inline get() = wo 926 | inline set(value) { 927 | builder.setCacheInputStream(value) 928 | } 929 | 930 | var cacheOutputStream: OutputStream 931 | @Deprecated( 932 | "Write only", level = DeprecationLevel.HIDDEN 933 | ) inline get() = wo 934 | inline set(value) { 935 | builder.setCacheOutputStream(value) 936 | } 937 | 938 | var cacheChecker: (Map) -> Boolean 939 | @Deprecated( 940 | "Write only", level = DeprecationLevel.HIDDEN 941 | ) inline get() = wo 942 | inline set(crossinline value) { 943 | builder.setCacheChecker { 944 | value(it) 945 | } 946 | } 947 | 948 | inline fun methods(crossinline init: MethodMatcherKt.() -> Unit) = 949 | MethodLazySequenceKt(builder.methods { 950 | MethodMatcherKt(it).init() 951 | }) 952 | 953 | inline fun firstMethod(crossinline init: MethodMatcherKt.() -> Unit) = 954 | MethodMatchKt(builder.firstMethod { 955 | MethodMatcherKt(it).init() 956 | }) 957 | 958 | inline fun classes(crossinline init: ClassMatcherKt.() -> Unit) = 959 | ClassLazySequenceKt(builder.classes { 960 | ClassMatcherKt(it).init() 961 | }) 962 | 963 | inline fun firstClass(crossinline init: ClassMatcherKt.() -> Unit) = 964 | ClassMatchKt(builder.firstClass { 965 | ClassMatcherKt(it).init() 966 | }) 967 | 968 | inline fun fields(crossinline init: FieldMatcherKt.() -> Unit) = 969 | FieldLazySequenceKt(builder.fields { 970 | FieldMatcherKt(it).init() 971 | }) 972 | 973 | inline fun firstField(crossinline init: FieldMatcherKt.() -> Unit) = 974 | FieldMatchKt(builder.firstField { 975 | FieldMatcherKt(it).init() 976 | }) 977 | 978 | inline fun constructors(crossinline init: ConstructorMatcherKt.() -> Unit) = 979 | ConstructorLazySequenceKt(builder.constructors { 980 | ConstructorMatcherKt(it).init() 981 | }) 982 | 983 | inline fun firstConstructor(crossinline init: ConstructorMatcherKt.() -> Unit) = 984 | ConstructorMatchKt(builder.firstConstructor { 985 | ConstructorMatcherKt(it).init() 986 | }) 987 | 988 | val String.exact: StringMatchKt 989 | inline get() = StringMatchKt(builder.exact(this)) 990 | val Class<*>.exact: ClassMatchKt 991 | inline get() = ClassMatchKt(builder.exact(this)) 992 | val Method.exact: MethodMatchKt 993 | inline get() = MethodMatchKt(builder.exact(this)) 994 | val Constructor<*>.exact: ConstructorMatchKt 995 | inline get() = ConstructorMatchKt(builder.exact(this)) 996 | val Field.exact: FieldMatchKt 997 | inline get() = FieldMatchKt(builder.exact(this)) 998 | val String.prefix: StringMatchKt 999 | inline get() = StringMatchKt(builder.prefix(this)) 1000 | val String.firstPrefix: StringMatchKt 1001 | inline get() = StringMatchKt(builder.firstPrefix(this)) 1002 | val String.exactClass: ClassMatchKt 1003 | inline get() = ClassMatchKt(builder.exactClass(this)) 1004 | val String.exactMethod: MethodMatchKt 1005 | inline get() = MethodMatchKt(builder.exactMethod(this)) 1006 | val String.exactConstructor: ConstructorMatchKt 1007 | inline get() = ConstructorMatchKt(builder.exactConstructor(this)) 1008 | val String.exactField: FieldMatchKt 1009 | inline get() = FieldMatchKt(builder.exactField(this)) 1010 | } 1011 | 1012 | inline fun XposedInterface.buildHooks( 1013 | classLoader: BaseDexClassLoader, sourcePath: String, crossinline init: HookBuilderKt.() -> Unit 1014 | ) = buildHooks(this, classLoader, sourcePath) { 1015 | HookBuilderKt(it).init() 1016 | } 1017 | --------------------------------------------------------------------------------