├── app
├── .gitignore
├── src
│ └── main
│ │ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── eirv
│ │ │ └── disablelsposed
│ │ │ ├── App.kt
│ │ │ ├── AppFactory.kt
│ │ │ ├── Native.kt
│ │ │ └── MainActivity.kt
│ │ ├── cpp
│ │ ├── third-party
│ │ │ └── lsplant
│ │ │ │ ├── type_traits.hpp
│ │ │ │ ├── LICENSE
│ │ │ │ └── jni_helper.hpp
│ │ ├── descriptor_builder.h
│ │ ├── .clang-format
│ │ ├── CMakeLists.txt
│ │ ├── file_reader.h
│ │ ├── descriptor_builder.cc
│ │ └── disable_lsposed.cc
│ │ ├── res
│ │ ├── values-zh
│ │ │ └── strings.xml
│ │ └── values
│ │ │ └── strings.xml
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle.kts
├── module
├── .gitignore
├── src
│ ├── legacy
│ │ ├── assets
│ │ │ └── xposed_init
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── eirv
│ │ │ └── replacetext
│ │ │ └── legacy
│ │ │ └── HookEntry.kt
│ ├── newapi
│ │ ├── resources
│ │ │ └── META-INF
│ │ │ │ └── xposed
│ │ │ │ ├── scope.list
│ │ │ │ ├── java_init.list
│ │ │ │ └── module.prop
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ │ └── io
│ │ │ └── github
│ │ │ └── eirv
│ │ │ └── replacetext
│ │ │ └── newapi
│ │ │ └── HookEntry.kt
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.webp
│ │ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ └── themes.xml
│ │ ├── mipmap-anydpi
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── xml
│ │ │ ├── backup_rules.xml
│ │ │ └── data_extraction_rules.xml
│ │ └── drawable
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ └── ic_launcher_background.xml
│ │ ├── kotlin
│ │ └── io
│ │ │ └── github
│ │ │ └── eirv
│ │ │ └── replacetext
│ │ │ ├── MainActivity.kt
│ │ │ └── TextViewHandler.kt
│ │ └── AndroidManifest.xml
├── build.gradle.kts
└── proguard-rules.pro
├── imgs
├── screenshot_en.jpg
└── screenshot_zh.jpg
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── .gitignore
├── .gitmodules
├── .gitattributes
├── README_zh.md
├── README.md
├── settings.gradle.kts
├── gradle.properties
├── gradlew.bat
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/module/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/module/src/legacy/assets/xposed_init:
--------------------------------------------------------------------------------
1 | io.github.eirv.replacetext.legacy.HookEntry
2 |
--------------------------------------------------------------------------------
/module/src/newapi/resources/META-INF/xposed/scope.list:
--------------------------------------------------------------------------------
1 | io.github.eirv.disablelsposed
2 |
--------------------------------------------------------------------------------
/module/src/newapi/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/imgs/screenshot_en.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/imgs/screenshot_en.jpg
--------------------------------------------------------------------------------
/imgs/screenshot_zh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/imgs/screenshot_zh.jpg
--------------------------------------------------------------------------------
/module/src/newapi/resources/META-INF/xposed/java_init.list:
--------------------------------------------------------------------------------
1 | io.github.eirv.replacetext.newapi.HookEntry
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/module/src/newapi/resources/META-INF/xposed/module.prop:
--------------------------------------------------------------------------------
1 | minApiVersion=100
2 | targetApiVersion=100
3 | staticScope=true
4 |
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eirv/DisableLSPosed/HEAD/module/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/module/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ReplaceText
3 | Hello, World!
4 |
--------------------------------------------------------------------------------
/module/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "app/src/main/cpp/third-party/linux-syscall-support"]
2 | path = app/src/main/cpp/third-party/linux-syscall-support
3 | url = https://github.com/eirv/linux-syscall-support
4 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/io/github/eirv/disablelsposed/App.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.disablelsposed
2 |
3 | import android.app.Application
4 |
5 | class App : Application() {
6 | companion object {
7 | init {
8 | Native.getFlags()
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # Linux start script should use lf
5 | /gradlew text eol=lf
6 |
7 | # These are Windows script files and should use crlf
8 | *.bat text eol=crlf
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/io/github/eirv/disablelsposed/AppFactory.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.disablelsposed
2 |
3 | import android.app.AppComponentFactory
4 |
5 | class AppFactory : AppComponentFactory() {
6 | companion object {
7 | init {
8 | Native.getFlags()
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://downloads.gradle.org/distributions/gradle-9.2.1-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/module/src/main/kotlin/io/github/eirv/replacetext/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.replacetext
2 |
3 | import android.os.Bundle
4 | import android.app.Activity
5 |
6 | class MainActivity : Activity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | setContentView(R.layout.activity_main)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/module/src/main/kotlin/io/github/eirv/replacetext/TextViewHandler.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.replacetext
2 |
3 | object TextViewHandler {
4 | fun handleSetText(args: Array) {
5 | args.getOrNull(0)?.let { firstArg ->
6 | if (firstArg is String && !firstArg.endsWith("[Hooked]")) {
7 | args[0] = "$firstArg[Hooked]"
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-anydpi/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/module/src/main/res/mipmap-anydpi/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | # DisableLSPosed
2 |
3 | [**English**](README.md)
4 |
5 | 让 [LSPosed](https://github.com/LSPosed/LSPosed)/[LSPatch](https://github.com/LSPosed/LSPatch) 失效并恢复所有被 [LSPlant](https://github.com/LSPosed/LSPlant) hook 的方法
6 |
7 | * 使 `LSPosed` 无法 hook 任何方法
8 | * 阻止调用 `IXposedHookLoadPackage::handleLoadPackage` 回调
9 | * 恢复先前被 `LSPosed (LSPlant)` hook 的方法
10 | * 恢复内存中对于 `libart.so` 的 inline hook
11 |
12 | 代码我乱写的, 仅供娱乐
13 |
14 |
15 |
--------------------------------------------------------------------------------
/module/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/cpp/third-party/lsplant/type_traits.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace lsplant {
6 | template class>
7 | struct is_instance : public std::false_type {};
8 |
9 | template class U>
10 | struct is_instance, U> : public std::true_type {};
11 |
12 | template class U>
13 | inline constexpr bool is_instance_v = is_instance::value;
14 | } // namespace lsplant
15 |
--------------------------------------------------------------------------------
/module/src/legacy/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/module/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/cpp/descriptor_builder.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class DescriptorBuilder {
6 | public:
7 | explicit DescriptorBuilder(JNIEnv* env);
8 |
9 | static jobject GetDescriptor(JNIEnv* env,
10 | jobject declaringClass,
11 | jstring name,
12 | jobjectArray parameterTypes,
13 | jobject returnType,
14 | jint modifiers);
15 |
16 | ~DescriptorBuilder();
17 |
18 | private:
19 | JNIEnv* env_;
20 | };
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | "
4 | 尝试禁用 %1$s: %2$s
5 |
6 | 尝试恢复对 libart.so 的 inline hook: %3$s
7 |
8 | 移除了 <font color='#FFFFFF'>%4$d</font> 个 Xposed 回调:
9 | <font color='#FFFFFF'>%5$s</font>
10 | 恢复了 <font color='#FFFFFF'>%6$d</font> 个被 LSPlant hook 的方法:
11 | "
12 | "<font color='green'>成功</font>"
13 | "<font color='red'>失败</font>"
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | "
4 | Attempt to disable %1$s: %2$s
5 |
6 | Attempt to restore inline hook for libart.so: %3$s
7 |
8 | Removed <font color='#FFFFFF'>%4$d</font> Xposed callbacks:
9 | <font color='#FFFFFF'>%5$s</font>
10 | Restored <font color='#FFFFFF'>%6$d</font> methods hooked by LSPlant:
11 | "
12 | "<font color='green'>Success</font>"
13 | "<font color='red'>Failure</font>"
14 |
15 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.13.1"
3 | kotlin = "2.2.21"
4 | cxx = "29.0.13599879-beta2"
5 | xposed-api = "82"
6 | xposed-newapi = "55efdf9"
7 |
8 | [libraries]
9 | cxx = { group = "org.lsposed.libcxx", name = "libcxx", version.ref = "cxx" }
10 | xposed-api = { group = "de.robv.android.xposed", name = "api", version.ref = "xposed-api" }
11 | xposed-newapi = { group = "io.github.libxposed", name = "api", version.ref = "xposed-newapi" }
12 |
13 | [plugins]
14 | android-application = { id = "com.android.application", version.ref = "agp" }
15 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DisableLSPosed
2 |
3 | [**中文**](README_zh.md)
4 |
5 | Disable [LSPosed](https://github.com/LSPosed/LSPosed)/[LSPatch](https://github.com/LSPosed/LSPatch) and restore all methods hooked by [LSPlant](https://github.com/LSPosed/LSPlant).
6 |
7 | * Prevent `LSPosed` from hooking any method
8 | * Block calls to the `IXposedHookLoadPackage::handleLoadPackage` callback
9 | * Restore methods previously hooked by `LSPosed (LSPlant)`
10 | * Restore inline hooks to `libart.so` in memory
11 |
12 | The code was written casually and is for entertainment purposes only.
13 |
14 |
15 |
--------------------------------------------------------------------------------
/module/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/cpp/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | BasedOnStyle: Google
3 | ---
4 |
5 | Language: Cpp
6 |
7 | AlignConsecutiveMacros: AcrossComments
8 | AllowShortBlocksOnASingleLine: Empty
9 | AllowShortCaseLabelsOnASingleLine: false
10 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse
11 | AllowShortLoopsOnASingleLine: false
12 | BinPackArguments: false
13 | BinPackParameters: false
14 | BreakConstructorInitializers: BeforeColon
15 | BreakBeforeTernaryOperators: false
16 | ColumnLimit: 120
17 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
18 | Cpp11BracedListStyle: true
19 | DerivePointerAlignment: false
20 | FixNamespaceComments: true
21 | PointerAlignment: Left
22 | TabWidth: 2
23 |
24 | TypenameMacros: ['ElfW']
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/module/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontobfuscate
24 | -keepattributes SourceFile,LineNumberTable
25 |
26 | -keep class io.github.eirv.disablelsposed.Native { native *(...); }
27 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | if ("CN" == System.getProperty("user.country")) {
4 | maven("https://maven.aliyun.com/repository/public/")
5 | maven("https://maven.aliyun.com/repository/google/")
6 | maven("https://maven.aliyun.com/repository/gradle-plugin/")
7 | }
8 | google {
9 | content {
10 | includeGroupByRegex("com\\.android.*")
11 | includeGroupByRegex("com\\.google.*")
12 | includeGroupByRegex("androidx.*")
13 | }
14 | }
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 | dependencyResolutionManagement {
20 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
21 | repositories {
22 | if ("CN" == System.getProperty("user.country")) {
23 | maven("https://maven.aliyun.com/repository/public/")
24 | maven("https://maven.aliyun.com/repository/google/")
25 | }
26 | google()
27 | mavenCentral()
28 | maven("https://jitpack.io")
29 | maven("https://api.xposed.info/")
30 | }
31 | }
32 |
33 | rootProject.name = "DisableLSPosed"
34 | include(":app")
35 | include(":module")
36 |
37 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/module/src/legacy/kotlin/io/github/eirv/replacetext/legacy/HookEntry.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.replacetext.legacy
2 |
3 | import android.widget.TextView
4 | import de.robv.android.xposed.IXposedHookLoadPackage
5 | import de.robv.android.xposed.IXposedHookZygoteInit
6 | import de.robv.android.xposed.XC_MethodHook
7 | import de.robv.android.xposed.XposedBridge
8 | import de.robv.android.xposed.callbacks.XC_LoadPackage
9 | import io.github.eirv.replacetext.TextViewHandler
10 |
11 | class HookEntry : XC_MethodHook(), IXposedHookZygoteInit, IXposedHookLoadPackage {
12 | override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
13 | XposedBridge.log("HookEntry::initZygote(modulePath=${startupParam.modulePath}, startsSystemServer=${startupParam.startsSystemServer})")
14 | XposedBridge.hookAllMethods(TextView::class.java, "setText", this)
15 | }
16 |
17 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
18 | XposedBridge.log("HookEntry::handleLoadPackage(packageName=${lpparam.packageName}, processName=${lpparam.processName}, classLoader=${lpparam.classLoader}, appInfo=${lpparam.appInfo}, isFirstApplication=${lpparam.isFirstApplication})")
19 | }
20 |
21 | override fun beforeHookedMethod(param: MethodHookParam) {
22 | TextViewHandler.handleSetText(param.args)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/module/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.android.application)
5 | alias(libs.plugins.kotlin.android)
6 | }
7 |
8 | android {
9 | namespace = "io.github.eirv.replacetext"
10 | compileSdk {
11 | version = release(36)
12 | }
13 |
14 | defaultConfig {
15 | applicationId = "io.github.eirv.replacetext"
16 | minSdk = 26
17 | targetSdk = 36
18 | versionCode = 1
19 | versionName = "1.0"
20 | }
21 |
22 | buildTypes {
23 | release {
24 | isMinifyEnabled = true
25 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
26 | signingConfig = signingConfigs.getByName("debug")
27 | }
28 | }
29 |
30 | flavorDimensions += "api"
31 | productFlavors {
32 | create("newapi") {
33 | dimension = "api"
34 | applicationIdSuffix = ".newapi"
35 | versionNameSuffix = "-newapi"
36 | }
37 | create("legacy") {
38 | dimension = "api"
39 | applicationIdSuffix = ".legacy"
40 | versionNameSuffix = "-legacy"
41 | }
42 | }
43 |
44 | compileOptions {
45 | sourceCompatibility = JavaVersion.VERSION_11
46 | targetCompatibility = JavaVersion.VERSION_11
47 | }
48 | kotlin {
49 | target {
50 | compilerOptions {
51 | jvmTarget = JvmTarget.JVM_11
52 | }
53 | }
54 | }
55 | lint {
56 | checkReleaseBuilds = false
57 | }
58 | }
59 |
60 | dependencies {
61 | "newapiCompileOnly"(libs.xposed.newapi)
62 | "legacyCompileOnly"(libs.xposed.api)
63 | }
64 |
--------------------------------------------------------------------------------
/module/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontobfuscate
24 | -keepattributes SourceFile,LineNumberTable
25 |
26 | -adaptresourcefilecontents META-INF/xposed/java_init.list
27 | -keep public class * implements de.robv.android.xposed.IXposedHookZygoteInit {
28 | public ();
29 | }
30 | -keep public class * implements de.robv.android.xposed.IXposedHookLoadPackage {
31 | public ();
32 | }
33 | -keep,allowobfuscation,allowoptimization public class * extends io.github.libxposed.api.XposedModule {
34 | public (...);
35 | public void onPackageLoaded(...);
36 | public void onSystemServerLoaded(...);
37 | }
38 | -keepclassmembers class * implements io.github.libxposed.api.XposedInterface$Hooker {
39 | *** before(...);
40 | *** after(...);
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/io/github/eirv/disablelsposed/Native.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.disablelsposed
2 |
3 | import android.util.Log
4 |
5 | object Native {
6 | private val loaded: Boolean
7 |
8 | init {
9 | var ok = false
10 | try {
11 | System.loadLibrary("disablelsposed")
12 | ok = true
13 | } catch (e: UnsatisfiedLinkError) {
14 | Log.e("Native", "Native library failed to load", e)
15 | }
16 | loaded = ok
17 | }
18 |
19 | @JvmStatic
20 | fun getFlags(): Int {
21 | if (!loaded) return 0
22 | return nGetFlags()
23 | }
24 |
25 | @JvmStatic
26 | fun getUnhookedMethods(): Array {
27 | if (!loaded) return emptyArray()
28 | return nGetUnhookedMethods()
29 | }
30 |
31 | @JvmStatic
32 | fun getUnhookedMethodList(): ArrayList {
33 | if (!loaded) return arrayListOf()
34 | return nGetUnhookedMethodList()
35 | }
36 |
37 | @JvmStatic
38 | fun getClearedCallbacks(): Array {
39 | if (!loaded) return emptyArray()
40 | return nGetClearedCallbacks()
41 | }
42 |
43 | @JvmStatic
44 | fun getFrameworkName(): String {
45 | if (!loaded) return "LSPosed"
46 | return nGetFrameworkName()
47 | }
48 |
49 | @JvmStatic
50 | private external fun nGetFlags(): Int
51 |
52 | @JvmStatic
53 | private external fun nGetUnhookedMethods(): Array
54 |
55 | @JvmStatic
56 | private external fun nGetUnhookedMethodList(): ArrayList
57 |
58 | @JvmStatic
59 | private external fun nGetClearedCallbacks(): Array
60 |
61 | @JvmStatic
62 | private external fun nGetFrameworkName(): String
63 | }
64 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 |
3 | plugins {
4 | alias(libs.plugins.android.application)
5 | alias(libs.plugins.kotlin.android)
6 | }
7 |
8 | android {
9 | namespace = "io.github.eirv.disablelsposed"
10 | ndkVersion = "29.0.14206865"
11 |
12 | compileSdk {
13 | version = release(36)
14 | }
15 |
16 | defaultConfig {
17 | applicationId = "io.github.eirv.disablelsposed"
18 | minSdk = 33
19 | targetSdk = 36
20 | versionCode = 10003
21 | versionName = "1.3"
22 |
23 | externalNativeBuild {
24 | cmake {
25 | arguments += "-DANDROID_STL=none"
26 | cppFlags += "-std=c++23"
27 | abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64", "riscv64")
28 | }
29 | }
30 | }
31 |
32 | buildTypes {
33 | release {
34 | isMinifyEnabled = true
35 | proguardFiles(
36 | getDefaultProguardFile("proguard-android-optimize.txt"),
37 | "proguard-rules.pro"
38 | )
39 | signingConfig = signingConfigs.getByName("debug")
40 | }
41 | }
42 | buildFeatures {
43 | prefab = true
44 | }
45 | compileOptions {
46 | sourceCompatibility = JavaVersion.VERSION_11
47 | targetCompatibility = JavaVersion.VERSION_11
48 | }
49 | kotlin {
50 | target {
51 | compilerOptions {
52 | jvmTarget = JvmTarget.JVM_11
53 | }
54 | }
55 | }
56 | externalNativeBuild {
57 | cmake {
58 | path = file("src/main/cpp/CMakeLists.txt")
59 | version = "3.22.1"
60 | }
61 | }
62 | lint {
63 | checkReleaseBuilds = false
64 | }
65 | }
66 |
67 | dependencies {
68 | compileOnly(libs.cxx)
69 | }
70 |
--------------------------------------------------------------------------------
/module/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/module/src/newapi/kotlin/io/github/eirv/replacetext/newapi/HookEntry.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.replacetext.newapi
2 |
3 | import android.os.Build
4 | import android.widget.TextView
5 | import io.github.eirv.replacetext.TextViewHandler
6 | import io.github.libxposed.api.XposedInterface
7 | import io.github.libxposed.api.XposedModule
8 | import io.github.libxposed.api.XposedModuleInterface.ModuleLoadedParam
9 | import io.github.libxposed.api.XposedModuleInterface.PackageLoadedParam
10 |
11 | class HookEntry(base: XposedInterface, param: ModuleLoadedParam) : XposedModule(base, param) {
12 | init {
13 | log("HookEntry::HookEntry(base=${base}, param.isSystemServer=${param.isSystemServer}, param.processName=${param.processName})")
14 | for (method in TextView::class.java.getDeclaredMethods()) {
15 | if (method.name != "setText") continue
16 | if (method.parameterCount == 0) continue
17 | if (method.getParameterTypes()[0] != CharSequence::class.java) continue
18 | hook(method, TextViewHook::class.java)
19 | }
20 | }
21 |
22 | override fun onPackageLoaded(param: PackageLoadedParam) {
23 | val sb = StringBuilder()
24 | sb.append("HookEntry::onPackageLoaded(packageName=")
25 | sb.append(param.packageName)
26 | sb.append(", applicationInfo=")
27 | sb.append(param.applicationInfo)
28 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
29 | sb.append(", defaultClassLoader=")
30 | sb.append(param.defaultClassLoader)
31 | }
32 | sb.append(", classLoader=")
33 | sb.append(param.classLoader)
34 | sb.append(", isFirstPackage=")
35 | sb.append(param.isFirstPackage)
36 | sb.append(")")
37 | log(sb.toString())
38 | }
39 |
40 | class TextViewHook : XposedInterface.Hooker {
41 | companion object {
42 | @Suppress("unused")
43 | @JvmStatic
44 | fun before(callback: XposedInterface.BeforeHookCallback) {
45 | TextViewHandler.handleSetText(callback.args)
46 | }
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html.
3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples.
4 |
5 | # Sets the minimum CMake version required for this project.
6 | cmake_minimum_required(VERSION 3.22.1)
7 |
8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
9 | # Since this is the top level CMakeLists.txt, the project name is also accessible
10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
11 | # build script scope).
12 | project("disablelsposed")
13 |
14 | find_package(cxx REQUIRED CONFIG)
15 | link_libraries(cxx::cxx)
16 |
17 | # Creates and names a library, sets it as either STATIC
18 | # or SHARED, and provides the relative paths to its source code.
19 | # You can define multiple libraries, and CMake builds them for you.
20 | # Gradle automatically packages shared libraries with your APK.
21 | #
22 | # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
23 | # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
24 | # is preferred for the same purpose.
25 | #
26 | # In order to load a library into your app from Java/Kotlin, you must call
27 | # System.loadLibrary() and pass the name of the library defined here;
28 | # for GameActivity/NativeActivity derived applications, the same library name must be
29 | # used in the AndroidManifest.xml file.
30 | add_library(${CMAKE_PROJECT_NAME} SHARED
31 | # List C/C++ source files with relative paths to this CMakeLists.txt.
32 | disable_lsposed.cc
33 | descriptor_builder.cc)
34 |
35 | target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
36 | .
37 | third-party/linux-syscall-support
38 | third-party/lsplant)
39 |
40 | # Specifies libraries CMake should link to your target library. You
41 | # can link libraries from various origins, such as libraries defined in this
42 | # build script, prebuilt third-party libraries, or Android system libraries.
43 | target_link_libraries(${CMAKE_PROJECT_NAME}
44 | # List libraries link to the target library
45 | log dl)
46 |
47 | target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE
48 | -Wall
49 | -fno-exceptions
50 | -fno-rtti
51 | -Oz
52 | -flto
53 | -fdata-sections
54 | -ffunction-sections
55 | -fno-stack-protector
56 | -fvisibility=hidden
57 | -fvisibility-inlines-hidden
58 | -fomit-frame-pointer
59 | -fno-unwind-tables
60 | -fno-asynchronous-unwind-tables)
61 | target_link_options(${CMAKE_PROJECT_NAME} PRIVATE
62 | -Wl,-exclude-libs,ALL
63 | -Wl,--as-needed
64 | -Wl,--gc-sections
65 | -Wl,--icf=all)
66 |
--------------------------------------------------------------------------------
/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 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 |
74 |
75 | @rem Execute Gradle
76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
77 |
78 | :end
79 | @rem End local scope for the variables with windows NT shell
80 | if %ERRORLEVEL% equ 0 goto mainEnd
81 |
82 | :fail
83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
84 | rem the _cmd.exe /c_ return code!
85 | set EXIT_CODE=%ERRORLEVEL%
86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
88 | exit /b %EXIT_CODE%
89 |
90 | :mainEnd
91 | if "%OS%"=="Windows_NT" endlocal
92 |
93 | :omega
94 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/io/github/eirv/disablelsposed/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.eirv.disablelsposed
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.os.Process
7 | import android.text.Html
8 | import android.text.SpannableStringBuilder
9 | import android.view.ViewGroup.LayoutParams.MATCH_PARENT
10 | import android.widget.HorizontalScrollView
11 | import android.widget.ScrollView
12 | import android.widget.TextView
13 |
14 | class MainActivity : Activity() {
15 |
16 | object Keys {
17 | private val NAME = MainActivity::class.java.name
18 | val METHOD_LIST = "$NAME::methodList"
19 | val FLAGS = "$NAME::flags"
20 | val CLEARED_CALLBACKS = "$NAME::clearedCallbacks"
21 | val FRAMEWORK_NAME = "$NAME::frameworkName"
22 | }
23 |
24 | private var methodList: ArrayList? = null
25 | private var flags: Int = 0
26 | private lateinit var clearedCallbacks: Array
27 | private lateinit var frameworkName: String
28 |
29 | companion object {
30 | init {
31 | Native.getFlags()
32 | }
33 | }
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 |
38 | actionBar?.subtitle = "Pid: ${Process.myPid()}"
39 |
40 | if (savedInstanceState != null) {
41 | restoreState(savedInstanceState)
42 | } else {
43 | loadNativeInfo()
44 | }
45 |
46 | val text = buildDisplayText()
47 |
48 | val scrollView = ScrollView(this).apply {
49 | isFillViewport = true
50 | }
51 | val horizontalScroll = HorizontalScrollView(this).apply {
52 | isFillViewport = true
53 | }
54 |
55 | val textView = TextView(this).apply {
56 | setTextIsSelectable(true)
57 | this.text = text
58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
59 | fitsSystemWindows = true
60 | }
61 | }
62 |
63 | horizontalScroll.addView(textView, MATCH_PARENT, MATCH_PARENT)
64 | scrollView.addView(horizontalScroll, MATCH_PARENT, MATCH_PARENT)
65 |
66 | setContentView(scrollView)
67 | }
68 |
69 | private fun restoreState(state: Bundle) {
70 | methodList = state.getCharSequenceArrayList(Keys.METHOD_LIST)
71 | flags = state.getInt(Keys.FLAGS)
72 | clearedCallbacks = state.getStringArray(Keys.CLEARED_CALLBACKS) ?: emptyArray()
73 | frameworkName = state.getString(Keys.FRAMEWORK_NAME) ?: ""
74 | }
75 |
76 | private fun loadNativeInfo() {
77 | val ml = Native.getUnhookedMethodList()
78 | methodList = when {
79 | ml != null -> ml
80 | else -> ArrayList(Native.getUnhookedMethods().toList())
81 | }
82 |
83 | flags = Native.getFlags()
84 | clearedCallbacks = Native.getClearedCallbacks()
85 | frameworkName = Native.getFrameworkName()
86 | }
87 |
88 | private fun buildDisplayText(): SpannableStringBuilder {
89 | val success = getString(R.string.success)
90 | val failure = getString(R.string.failure)
91 |
92 | val clearedText = buildString {
93 | clearedCallbacks.forEach {
94 | append("\n")
95 | append(it)
96 | }
97 | if (isNotEmpty()) append("\n")
98 | }
99 |
100 | val header = Html.fromHtml(
101 | getString(
102 | R.string.message,
103 | frameworkName,
104 | if (flags and 1 != 0) success else failure,
105 | if (flags and (1 shl 1) != 0) success else failure,
106 | clearedCallbacks.size,
107 | clearedText,
108 | methodList?.size ?: 0
109 | ).replace("\n", "
"), Html.FROM_HTML_MODE_LEGACY
110 | )
111 |
112 | return SpannableStringBuilder().apply {
113 | append(header)
114 |
115 | methodList?.forEach {
116 | append("\n")
117 | append(it)
118 | }
119 | }
120 | }
121 |
122 | override fun onSaveInstanceState(outState: Bundle) {
123 | super.onSaveInstanceState(outState)
124 | outState.putCharSequenceArrayList(Keys.METHOD_LIST, methodList)
125 | outState.putInt(Keys.FLAGS, flags)
126 | outState.putStringArray(Keys.CLEARED_CALLBACKS, clearedCallbacks)
127 | outState.putString(Keys.FRAMEWORK_NAME, frameworkName)
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/module/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/cpp/third-party/lsplant/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2022 LSPosed
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/app/src/main/cpp/file_reader.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "linux_syscall_support.h"
16 |
17 | namespace io {
18 |
19 | template
20 | class BaseIterator {
21 | public:
22 | using iterator_category = std::input_iterator_tag;
23 | using value_type = typename Reader::value_type;
24 | using difference_type = std::ptrdiff_t;
25 | using reference = value_type&;
26 | using const_reference = const value_type&;
27 | using pointer = value_type*;
28 | using const_pointer = const value_type*;
29 |
30 | BaseIterator() = default;
31 | explicit BaseIterator(Reader* reader) : reader_{reader} { ++(*this); }
32 |
33 | auto operator*() const noexcept -> const_reference { return current_; }
34 | auto operator*() noexcept -> reference { return current_; }
35 | auto operator->() const noexcept -> const_pointer { return ¤t_; }
36 | auto operator->() noexcept -> pointer { return ¤t_; }
37 |
38 | auto operator++() noexcept -> auto& {
39 | if (!reader_) return *this;
40 | auto ret = **reader_;
41 | if (!ret) {
42 | reader_ = nullptr;
43 | current_ = {};
44 | } else {
45 | using Ret = decltype(std::declval().operator*());
46 | if constexpr (std::is_same_v>) {
47 | current_ = *ret;
48 | } else {
49 | current_ = ret;
50 | }
51 | }
52 | return *this;
53 | }
54 |
55 | auto operator++(int) noexcept {
56 | auto tmp = *this;
57 | ++(*this);
58 | return tmp;
59 | }
60 |
61 | bool operator==(const BaseIterator& other) const noexcept { return reader_ == other.reader_; }
62 |
63 | private:
64 | Reader* reader_{};
65 | value_type current_{};
66 | };
67 |
68 | template
69 | class BaseReader {
70 | public:
71 | using value_type = T;
72 | using iterator = BaseIterator;
73 |
74 | explicit BaseReader(int fd) : fd_{fd} {
75 | if constexpr (kUseHeap) {
76 | buffer_ = std::make_unique(kBufferSize);
77 | }
78 | }
79 |
80 | BaseReader(const BaseReader&) = delete;
81 | BaseReader& operator=(const BaseReader&) = delete;
82 |
83 | ~BaseReader() {
84 | if (fd_ >= 0) raw_close(fd_);
85 | }
86 |
87 | operator bool() const noexcept { return IsValid(); }
88 |
89 | auto IsValid() const noexcept { return fd_ >= 0; }
90 | auto GetFd() const noexcept { return fd_; }
91 |
92 | auto begin() { return iterator{static_cast(this)}; }
93 | auto end() { return iterator{}; }
94 |
95 | protected:
96 | template
97 | auto NextImpl(Parser&& parse_func) -> std::optional {
98 | if (fd_ < 0) return {};
99 |
100 | for (;;) {
101 | auto available = buf_end_ - buf_pos_;
102 |
103 | if (auto res = parse_func(&buffer_[buf_pos_], available)) {
104 | auto [val, consumed] = *res;
105 | buf_pos_ += consumed;
106 | if (buf_pos_ == buf_end_) buf_pos_ = buf_end_ = 0;
107 | return val;
108 | }
109 |
110 | if (buf_pos_ > 0 && buf_pos_ < buf_end_) {
111 | auto rem = buf_end_ - buf_pos_;
112 | memmove(&buffer_[0], &buffer_[buf_pos_], rem);
113 | buf_end_ = rem;
114 | buf_pos_ = 0;
115 | } else if (buf_pos_ == buf_end_) {
116 | buf_pos_ = buf_end_ = 0;
117 | }
118 |
119 | auto space = kBufferSize - buf_end_;
120 | if (space == 0) {
121 | return static_cast(this)->OnBufferFull(&buffer_[0], buf_end_);
122 | }
123 |
124 | ssize_t n;
125 | do {
126 | n = static_cast(this)->ReadFromFD(fd_, &buffer_[buf_end_], space);
127 | } while (n == -EINTR);
128 |
129 | if (n <= 0) {
130 | return static_cast(this)->OnEOF(&buffer_[0], buf_end_);
131 | }
132 |
133 | buf_end_ += static_cast(n);
134 | }
135 | }
136 |
137 | private:
138 | int fd_;
139 | size_t buf_pos_{};
140 | size_t buf_end_{};
141 | std::conditional_t, std::array> buffer_;
142 | };
143 |
144 | template
145 | class FileReader : public BaseReader, std::string_view, kBufferSize, kUseHeap> {
146 | public:
147 | explicit FileReader(const char* pathname) : FileReader{raw_open(pathname, O_RDONLY | O_CLOEXEC, 0)} {}
148 |
149 | explicit FileReader(int fd) : BaseReader{fd} {}
150 |
151 | auto operator*() { return NextLine(); }
152 |
153 | auto NextLine() {
154 | return this->NextImpl(+[](char* buf, size_t available) -> std::optional> {
155 | if (available == 0) return {};
156 |
157 | if (auto nl = static_cast(memchr(buf, '\n', available))) {
158 | *nl = '\0';
159 | auto len = static_cast(nl - buf);
160 | return std::pair{std::string_view{buf, len}, len + 1};
161 | }
162 | return {};
163 | });
164 | }
165 |
166 | private:
167 | auto OnBufferFull(char* buf, size_t sz) -> std::optional {
168 | buf[sz] = '\0';
169 | return std::string_view{buf, sz};
170 | }
171 |
172 | auto OnEOF(char* buf, size_t sz) -> std::optional {
173 | if (sz == 0) return {};
174 | buf[sz] = '\0';
175 | return std::string_view{buf, sz};
176 | }
177 |
178 | auto ReadFromFD(int fd, void* buf, size_t sz) { return raw_read(fd, buf, sz); }
179 |
180 | friend class BaseReader;
181 | };
182 |
183 | enum class DirEntryType : uint8_t {
184 | kUnknown = DT_UNKNOWN,
185 | kFIFO = DT_FIFO,
186 | kCharacterDevice = DT_CHR,
187 | kDirectory = DT_DIR,
188 | kBlockDevice = DT_BLK,
189 | kRegularFile = DT_REG,
190 | kSymbolicLink = DT_LNK,
191 | kSocket = DT_SOCK,
192 | };
193 |
194 | struct DirEntry {
195 | kernel_dirent64* entry;
196 |
197 | [[nodiscard]] auto inode() const { return entry->d_ino; }
198 | [[nodiscard]] auto offset() const { return entry->d_off; }
199 | [[nodiscard]] auto type() const { return static_cast(entry->d_type); }
200 | [[nodiscard]] auto name() const {
201 | return std::string_view{entry->d_name, strnlen(entry->d_name, entry->d_reclen - offsetof(kernel_dirent64, d_name))};
202 | }
203 |
204 | [[nodiscard]] auto is_unknown() const { return type() == DirEntryType::kUnknown; }
205 | [[nodiscard]] auto is_fifo() const { return type() == DirEntryType::kFIFO; }
206 | [[nodiscard]] auto is_character_device() const { return type() == DirEntryType::kCharacterDevice; }
207 | [[nodiscard]] auto is_directory() const { return type() == DirEntryType::kDirectory; }
208 | [[nodiscard]] auto is_block_device() const { return type() == DirEntryType::kBlockDevice; }
209 | [[nodiscard]] auto is_regular_file() const { return type() == DirEntryType::kRegularFile; }
210 | [[nodiscard]] auto is_symbolic_link() const { return type() == DirEntryType::kSymbolicLink; }
211 | [[nodiscard]] auto is_socket() const { return type() == DirEntryType::kSocket; }
212 | };
213 |
214 | template
215 | class DirReader : public BaseReader, DirEntry, kBufferSize, kUseHeap> {
216 | public:
217 | explicit DirReader(const char* pathname) : DirReader{raw_open(pathname, O_DIRECTORY | O_CLOEXEC, 0)} {}
218 |
219 | explicit DirReader(int fd) : BaseReader{fd} {}
220 |
221 | auto operator*() { return NextEntry(); }
222 |
223 | auto NextEntry() {
224 | return this->NextImpl(+[](char* buf, size_t available) -> std::optional> {
225 | if (available < offsetof(kernel_dirent64, d_name)) return {};
226 |
227 | auto dir = reinterpret_cast(buf);
228 | if (available < dir->d_reclen) return {};
229 |
230 | return std::pair{DirEntry{dir}, dir->d_reclen};
231 | });
232 | }
233 |
234 | private:
235 | auto OnBufferFull(char*, size_t) { return std::optional{}; }
236 |
237 | auto OnEOF(char*, size_t) { return std::optional{}; }
238 |
239 | auto ReadFromFD(int fd, void* buf, size_t sz) { return raw_getdents64(fd, static_cast(buf), sz); }
240 |
241 | friend class BaseReader;
242 | };
243 |
244 | } // namespace io
245 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015 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 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
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 |
118 |
119 | # Determine the Java command to use to start the JVM.
120 | if [ -n "$JAVA_HOME" ] ; then
121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
122 | # IBM's JDK on AIX uses strange locations for the executables
123 | JAVACMD=$JAVA_HOME/jre/sh/java
124 | else
125 | JAVACMD=$JAVA_HOME/bin/java
126 | fi
127 | if [ ! -x "$JAVACMD" ] ; then
128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
129 |
130 | Please set the JAVA_HOME variable in your environment to match the
131 | location of your Java installation."
132 | fi
133 | else
134 | JAVACMD=java
135 | if ! command -v java >/dev/null 2>&1
136 | then
137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
138 |
139 | Please set the JAVA_HOME variable in your environment to match the
140 | location of your Java installation."
141 | fi
142 | fi
143 |
144 | # Increase the maximum file descriptors if we can.
145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
146 | case $MAX_FD in #(
147 | max*)
148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
149 | # shellcheck disable=SC2039,SC3045
150 | MAX_FD=$( ulimit -H -n ) ||
151 | warn "Could not query maximum file descriptor limit"
152 | esac
153 | case $MAX_FD in #(
154 | '' | soft) :;; #(
155 | *)
156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
157 | # shellcheck disable=SC2039,SC3045
158 | ulimit -n "$MAX_FD" ||
159 | warn "Could not set maximum file descriptor limit to $MAX_FD"
160 | esac
161 | fi
162 |
163 | # Collect all arguments for the java command, stacking in reverse order:
164 | # * args from the command line
165 | # * the main class name
166 | # * -classpath
167 | # * -D...appname settings
168 | # * --module-path (only if needed)
169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
170 |
171 | # For Cygwin or MSYS, switch paths to Windows format before running java
172 | if "$cygwin" || "$msys" ; then
173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
214 | "$@"
215 |
216 | # Stop when "xargs" is not available.
217 | if ! command -v xargs >/dev/null 2>&1
218 | then
219 | die "xargs is not available"
220 | fi
221 |
222 | # Use "xargs" to parse quoted args.
223 | #
224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
225 | #
226 | # In Bash we could simply go:
227 | #
228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
229 | # set -- "${ARGS[@]}" "$@"
230 | #
231 | # but POSIX shell has neither arrays nor command substitution, so instead we
232 | # post-process each arg (as a line of input to sed) to backslash-escape any
233 | # character that might be a shell metacharacter, then use eval to reverse
234 | # that process (while maintaining the separation between arguments), and wrap
235 | # the whole thing up as a single "set" statement.
236 | #
237 | # This will of course break if any of these variables contains a newline or
238 | # an unmatched quote.
239 | #
240 |
241 | eval "set -- $(
242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
243 | xargs -n1 |
244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
245 | tr '\n' ' '
246 | )" '"$@"'
247 |
248 | exec "$JAVACMD" "$@"
249 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/cpp/descriptor_builder.cc:
--------------------------------------------------------------------------------
1 | // AIGC
2 | // ChatGPT converts Java code into equivalent C++ JNI code.
3 |
4 | #include "descriptor_builder.h"
5 |
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | enum class ColorScheme : uint32_t {
14 | kPunctuation = 0xFFD0D0D0u,
15 | kDescriptorL = 0xFFD0D0D0u,
16 | kDescriptorPrimitive = 0xFF00AFFFu,
17 | kDescriptorPackageName = 0xFF949494u,
18 | kDescriptorPackageNameSynthetic = 0xFF949494u,
19 | kDescriptorClassName = 0xFF00AFFFu,
20 | kDescriptorClassNameSynthetic = 0xFFD0D0D0u,
21 | kDescriptorMethodName = 0xFFFF8700u,
22 | kDescriptorMethodNameSynthetic = 0xFFD0D0D0u,
23 | kDescriptorSemicolon = 0xFFD0D0D0u,
24 | kDescriptorArrow = 0xFFFF0000u
25 | };
26 |
27 | struct Modifier {
28 | static constexpr jint SYNTHETIC = 0x1000;
29 | };
30 |
31 | struct Spanned {
32 | static constexpr jint SPAN_EXCLUSIVE_EXCLUSIVE = 0x21;
33 | };
34 |
35 | // ============ JNI CACHE ============
36 | // A small singleton that stores global refs and method IDs for fast calls.
37 | struct JniCache {
38 | bool initialized = false;
39 |
40 | // classes
41 | jclass SpannableStringBuilder_class = nullptr; // android.text.SpannableStringBuilder
42 | jclass ForegroundColorSpan_class = nullptr; // android.text.style.ForegroundColorSpan
43 | jclass Class_class = nullptr; // java.lang.Class
44 |
45 | // primitive TYPE class objects (global refs)
46 | jobject Boolean_TYPE = nullptr;
47 | jobject Byte_TYPE = nullptr;
48 | jobject Short_TYPE = nullptr;
49 | jobject Character_TYPE = nullptr;
50 | jobject Integer_TYPE = nullptr;
51 | jobject Float_TYPE = nullptr;
52 | jobject Long_TYPE = nullptr;
53 | jobject Double_TYPE = nullptr;
54 | jobject Void_TYPE = nullptr;
55 |
56 | // methods / constructors
57 | jmethodID SpannableStringBuilder_init = nullptr; // SpannableStringBuilder()
58 | jmethodID SpannableStringBuilder_append = nullptr; // append(String)
59 | jmethodID SpannableStringBuilder_length = nullptr; // length()
60 | jmethodID SpannableStringBuilder_setSpan = nullptr; // setSpan(Object, int, int, int)
61 |
62 | jmethodID ForegroundColorSpan_init = nullptr; // ForegroundColorSpan(int)
63 |
64 | jmethodID Class_isPrimitive = nullptr; // Class.isPrimitive()
65 | jmethodID Class_isSynthetic = nullptr; // Class.isSynthetic()
66 | jmethodID Class_isArray = nullptr; // Class.isArray()
67 | jmethodID Class_getName = nullptr; // Class.getName()
68 |
69 | // mutex for thread-safe init
70 | std::once_flag initFlag;
71 |
72 | static JniCache& instance() {
73 | static JniCache s;
74 | return s;
75 | }
76 |
77 | void init(JNIEnv* env) {
78 | std::call_once(initFlag, [&]() {
79 | // Find classes
80 | jclass local_ssb = env->FindClass("android/text/SpannableStringBuilder");
81 | jclass local_fcs = env->FindClass("android/text/style/ForegroundColorSpan");
82 | jclass local_class = env->FindClass("java/lang/Class");
83 | if (!local_ssb || !local_fcs || !local_class) {
84 | // If you want logging, add ALOG here. For brevity, we'll assert.
85 | env->ExceptionClear();
86 | assert(false && "Failed to find required Java classes");
87 | return;
88 | }
89 |
90 | // promote to global refs
91 | SpannableStringBuilder_class = local_ssb;
92 | ForegroundColorSpan_class = local_fcs;
93 | Class_class = local_class;
94 |
95 | // SpannableStringBuilder methods
96 | SpannableStringBuilder_init = env->GetMethodID(SpannableStringBuilder_class, "", "()V");
97 | SpannableStringBuilder_append = env->GetMethodID(
98 | SpannableStringBuilder_class, "append", "(Ljava/lang/CharSequence;)Landroid/text/SpannableStringBuilder;");
99 | SpannableStringBuilder_length = env->GetMethodID(SpannableStringBuilder_class, "length", "()I");
100 | SpannableStringBuilder_setSpan =
101 | env->GetMethodID(SpannableStringBuilder_class, "setSpan", "(Ljava/lang/Object;III)V");
102 | if (!SpannableStringBuilder_init || !SpannableStringBuilder_append || !SpannableStringBuilder_length ||
103 | !SpannableStringBuilder_setSpan) {
104 | env->ExceptionClear();
105 | assert(false && "Failed to find SpannableStringBuilder methods");
106 | }
107 |
108 | // ForegroundColorSpan ctor
109 | ForegroundColorSpan_init = env->GetMethodID(ForegroundColorSpan_class, "", "(I)V");
110 | if (!ForegroundColorSpan_init) {
111 | env->ExceptionClear();
112 | assert(false && "Failed to find ForegroundColorSpan ctor");
113 | }
114 |
115 | // Class methods
116 | Class_isPrimitive = env->GetMethodID(Class_class, "isPrimitive", "()Z");
117 | Class_isSynthetic = env->GetMethodID(Class_class, "isSynthetic", "()Z");
118 | Class_isArray = env->GetMethodID(Class_class, "isArray", "()Z");
119 | Class_getName = env->GetMethodID(Class_class, "getName", "()Ljava/lang/String;");
120 | if (!Class_isPrimitive || !Class_isSynthetic || !Class_isArray || !Class_getName) {
121 | env->ExceptionClear();
122 | assert(false && "Failed to find Class methods");
123 | }
124 |
125 | // Cache primitive Class objects via wrapper TYPE fields
126 | auto getPrimitiveTypeField = [&](const char* wrapperClassName) -> jobject {
127 | auto wrapperClass = env->FindClass(wrapperClassName);
128 | if (!wrapperClass) {
129 | env->ExceptionClear();
130 | return nullptr;
131 | }
132 | auto fid = env->GetStaticFieldID(wrapperClass, "TYPE", "Ljava/lang/Class;");
133 | if (!fid) {
134 | env->ExceptionClear();
135 | env->DeleteLocalRef(wrapperClass);
136 | return nullptr;
137 | }
138 | auto clsObj = env->GetStaticObjectField(wrapperClass, fid);
139 | env->DeleteLocalRef(wrapperClass);
140 | return clsObj;
141 | };
142 |
143 | Boolean_TYPE = getPrimitiveTypeField("java/lang/Boolean");
144 | Byte_TYPE = getPrimitiveTypeField("java/lang/Byte");
145 | Short_TYPE = getPrimitiveTypeField("java/lang/Short");
146 | Character_TYPE = getPrimitiveTypeField("java/lang/Character");
147 | Integer_TYPE = getPrimitiveTypeField("java/lang/Integer");
148 | Float_TYPE = getPrimitiveTypeField("java/lang/Float");
149 | Long_TYPE = getPrimitiveTypeField("java/lang/Long");
150 | Double_TYPE = getPrimitiveTypeField("java/lang/Double");
151 | Void_TYPE = getPrimitiveTypeField("java/lang/Void");
152 |
153 | initialized = true;
154 | });
155 | }
156 |
157 | // utility destructor to release global refs when process unloads (not strictly required but hygienic)
158 | void destroy(JNIEnv* env) {
159 | if (!initialized) return;
160 | env->DeleteLocalRef(SpannableStringBuilder_class);
161 | env->DeleteLocalRef(ForegroundColorSpan_class);
162 | env->DeleteLocalRef(Class_class);
163 |
164 | env->DeleteLocalRef(Boolean_TYPE);
165 | env->DeleteLocalRef(Byte_TYPE);
166 | env->DeleteLocalRef(Short_TYPE);
167 | env->DeleteLocalRef(Character_TYPE);
168 | env->DeleteLocalRef(Integer_TYPE);
169 | env->DeleteLocalRef(Float_TYPE);
170 | env->DeleteLocalRef(Long_TYPE);
171 | env->DeleteLocalRef(Double_TYPE);
172 | env->DeleteLocalRef(Void_TYPE);
173 | initialized = false;
174 | }
175 | };
176 |
177 | // ============ Helper functions ============
178 |
179 | // Append a string with color span: obtains start = ssb.length(); ssb.append(str); create ForegroundColorSpan(color);
180 | // ssb.setSpan(span, start, start+len, flags)
181 | static void appendStringWithColor(JNIEnv* env, jobject ssb, const std::string& str, ColorScheme color) {
182 | JniCache& C = JniCache::instance();
183 | // start index
184 | jint start = env->CallNonvirtualIntMethod(ssb, C.SpannableStringBuilder_class, C.SpannableStringBuilder_length);
185 | jstring jstr = env->NewStringUTF(str.c_str());
186 | // append
187 | env->CallNonvirtualObjectMethod(ssb, C.SpannableStringBuilder_class, C.SpannableStringBuilder_append, jstr);
188 | // create span object
189 | jobject spanObj = env->NewObject(C.ForegroundColorSpan_class, C.ForegroundColorSpan_init, static_cast(color));
190 | // set span (what, start, end, flags)
191 | jint end = start + static_cast(str.size());
192 | env->CallNonvirtualVoidMethod(ssb,
193 | C.SpannableStringBuilder_class,
194 | C.SpannableStringBuilder_setSpan,
195 | spanObj,
196 | start,
197 | end,
198 | Spanned::SPAN_EXCLUSIVE_EXCLUSIVE);
199 | // cleanup locals
200 | env->DeleteLocalRef(jstr);
201 | env->DeleteLocalRef(spanObj);
202 | }
203 |
204 | // Append primitive descriptor (single-letter) like "I", "Z", etc.
205 | static void appendPrimitiveDescriptor(JNIEnv* env, jobject ssb, jobject clazz) {
206 | JniCache& C = JniCache::instance();
207 | // compare clazz to cached TYPE objects
208 | auto name = "V";
209 | if (env->IsSameObject(clazz, C.Boolean_TYPE)) name = "Z";
210 | else if (env->IsSameObject(clazz, C.Byte_TYPE)) name = "B";
211 | else if (env->IsSameObject(clazz, C.Short_TYPE)) name = "S";
212 | else if (env->IsSameObject(clazz, C.Character_TYPE)) name = "C";
213 | else if (env->IsSameObject(clazz, C.Integer_TYPE)) name = "I";
214 | else if (env->IsSameObject(clazz, C.Float_TYPE)) name = "F";
215 | else if (env->IsSameObject(clazz, C.Long_TYPE)) name = "J";
216 | else if (env->IsSameObject(clazz, C.Double_TYPE)) name = "D";
217 | appendStringWithColor(env, ssb, name, ColorScheme::kDescriptorPrimitive);
218 | }
219 |
220 | // Split a fully-qualified class name by '.' into parts without allocations per piece (returns vector).
221 | static std::vector splitClassNameToParts(const std::string& name) {
222 | std::vector parts;
223 | parts.reserve(6);
224 | size_t off = 0;
225 | for (size_t i = 0; i < name.size(); ++i) {
226 | if (name[i] == '.') {
227 | parts.emplace_back(name.data() + off, i - off);
228 | off = i + 1;
229 | }
230 | }
231 | if (off <= name.size()) parts.emplace_back(name.data() + off, name.size() - off);
232 | return parts;
233 | }
234 |
235 | // Append class name representation like: Lcom/example/Foo;
236 | static void appendClassName(JNIEnv* env, jobject ssb, const std::string& className, bool syntheticFlag) {
237 | // append 'L'
238 | appendStringWithColor(env, ssb, "L", ColorScheme::kDescriptorL);
239 | // split on dot and append each package segment with "/" punctuation
240 | auto parts = splitClassNameToParts(className);
241 | size_t last = parts.size();
242 | for (size_t i = 0; i + 1 < last; ++i) {
243 | appendStringWithColor(
244 | env,
245 | ssb,
246 | parts[i],
247 | syntheticFlag ? ColorScheme::kDescriptorPackageNameSynthetic : ColorScheme::kDescriptorPackageName);
248 | appendStringWithColor(env, ssb, "/", ColorScheme::kPunctuation);
249 | }
250 | if (!parts.empty()) {
251 | appendStringWithColor(
252 | env,
253 | ssb,
254 | parts.back(),
255 | syntheticFlag ? ColorScheme::kDescriptorClassNameSynthetic : ColorScheme::kDescriptorClassName);
256 | }
257 | appendStringWithColor(env, ssb, ";", ColorScheme::kDescriptorSemicolon);
258 | }
259 |
260 | // Append a Class> descriptor: handles primitive, array, or object
261 | static void appendClassDescriptor(JNIEnv* env, jobject ssb, jobject clazz) {
262 | JniCache& C = JniCache::instance();
263 | // call isPrimitive()
264 | jboolean isPrim = env->CallNonvirtualBooleanMethod(clazz, C.Class_class, C.Class_isPrimitive);
265 | if (isPrim) {
266 | appendPrimitiveDescriptor(env, ssb, clazz);
267 | return;
268 | }
269 |
270 | // handle arrays / objects
271 | jboolean isArray = env->CallNonvirtualBooleanMethod(clazz, C.Class_class, C.Class_isArray);
272 | jboolean synthetic = env->CallNonvirtualBooleanMethod(clazz, C.Class_class, C.Class_isSynthetic);
273 | auto nameStr = reinterpret_cast(env->CallNonvirtualObjectMethod(clazz, C.Class_class, C.Class_getName));
274 | const char* cname = env->GetStringUTFChars(nameStr, nullptr);
275 | std::string name(cname);
276 | env->ReleaseStringUTFChars(nameStr, cname);
277 | env->DeleteLocalRef(nameStr);
278 |
279 | if (isArray) {
280 | // Java name for array classes looks like "[I", "[[Ljava.lang.String;" etc.
281 | // Find last '[' (should be leading)
282 | // size_t idx = name.find_last_of('[');
283 | // size_t index = (idx == std::string::npos) ? 0 : idx;
284 | // We want to append the leading '[' characters (there may be multiple)
285 | // In your Java code you appended substring(0, ++index) and if char at index == 'L' handle specially.
286 | // So compute count of '[':
287 | size_t countBrackets = 0;
288 | while (countBrackets < name.size() && name[countBrackets] == '[')
289 | ++countBrackets;
290 | // append that many '[' as punctuation (original used substring of name)
291 | std::string brackets(countBrackets, '[');
292 | appendStringWithColor(env, ssb, brackets, ColorScheme::kPunctuation);
293 |
294 | if (countBrackets < name.size()) {
295 | char typeChar = name[countBrackets];
296 | if (typeChar == 'L') {
297 | // then the rest is like Ljava.lang.String; -> we need inner class name between L and ;
298 | // remove leading 'L' and trailing ';' if present
299 | std::string inner = name.substr(countBrackets + 1);
300 | if (!inner.empty() && inner.back() == ';') inner.pop_back();
301 | appendClassName(env, ssb, inner, synthetic);
302 | } else {
303 | // primitive code like 'I', 'B', etc.
304 | std::string primChar(1, typeChar);
305 | appendStringWithColor(env, ssb, primChar, ColorScheme::kDescriptorPrimitive);
306 | }
307 | }
308 | } else {
309 | // normal class name, e.g., java.lang.String
310 | appendClassName(env, ssb, name, synthetic);
311 | }
312 | }
313 |
314 | // ============ Native method implementation ============
315 | // Signature assumes Java side will declare native as:
316 | // public static native SpannableStringBuilder getDescriptorNative(Class> declaringClass, String name, Class>[]
317 | // parameterTypes, Class> returnType, int modifiers);
318 | jobject DescriptorBuilder::GetDescriptor(JNIEnv* env,
319 | jobject declaringClass,
320 | jstring name,
321 | jobjectArray parameterTypes,
322 | jobject returnType,
323 | jint modifiers) {
324 | JniCache& C = JniCache::instance();
325 | C.init(env);
326 |
327 | // Create SpannableStringBuilder instance
328 | jobject ssb = env->NewObject(C.SpannableStringBuilder_class, C.SpannableStringBuilder_init);
329 |
330 | // append declaring class
331 | if (declaringClass != nullptr) {
332 | appendClassDescriptor(env, ssb, declaringClass);
333 | }
334 |
335 | // append arrow "->"
336 | appendStringWithColor(env, ssb, "->", ColorScheme::kDescriptorArrow);
337 |
338 | // append method name
339 | const char* nameChars = env->GetStringUTFChars(name, nullptr);
340 | std::string methodName(nameChars);
341 | env->ReleaseStringUTFChars(name, nameChars);
342 |
343 | // check synthetic modifier from Modifier.SYNTHETIC if available
344 | bool methodSynthetic = (modifiers & Modifier::SYNTHETIC) != 0;
345 | appendStringWithColor(
346 | env,
347 | ssb,
348 | methodName,
349 | methodSynthetic ? ColorScheme::kDescriptorMethodNameSynthetic : ColorScheme::kDescriptorMethodName);
350 |
351 | // append "("
352 | appendStringWithColor(env, ssb, "(", ColorScheme::kPunctuation);
353 |
354 | // parameters
355 | if (parameterTypes != nullptr) {
356 | jsize paramCount = env->GetArrayLength(parameterTypes);
357 | for (jsize i = 0; i < paramCount; ++i) {
358 | jobject pClazz = env->GetObjectArrayElement(parameterTypes, i); // local ref
359 | appendClassDescriptor(env, ssb, pClazz);
360 | env->DeleteLocalRef(pClazz);
361 | }
362 | }
363 |
364 | // append ")"
365 | appendStringWithColor(env, ssb, ")", ColorScheme::kPunctuation);
366 |
367 | // return type
368 | if (returnType != nullptr) {
369 | appendClassDescriptor(env, ssb, returnType);
370 | }
371 |
372 | // return SpannableStringBuilder (local ref ok)
373 | return ssb;
374 | }
375 |
376 | // ============ JNI OnLoad / OnUnload for init/cleanup ============
377 | DescriptorBuilder::DescriptorBuilder(JNIEnv* env) : env_{env} {
378 | // initialize cache
379 | JniCache::instance().init(env);
380 | }
381 |
382 | DescriptorBuilder::~DescriptorBuilder() { JniCache::instance().destroy(env_); }
383 |
--------------------------------------------------------------------------------
/app/src/main/cpp/disable_lsposed.cc:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | #include "descriptor_builder.h"
20 | #include "file_reader.h"
21 | #include "jni_helper.hpp"
22 | #include "linux_syscall_support.h"
23 |
24 | #define USE_SPANNABLE_STRING_BUILDER 1
25 |
26 | using namespace std::string_literals;
27 | using namespace std::string_view_literals;
28 | using namespace lsplant;
29 |
30 | #ifdef USE_SPANNABLE_STRING_BUILDER
31 | static jobject unhooked_method_list_{};
32 | #else
33 | static std::vector unhooked_methods_{};
34 | #endif
35 |
36 | static std::vector cleared_callbacks_{};
37 | static auto framework_name_ = "LSPosed"s;
38 |
39 | static bool is_lsposed_disabled_{};
40 | static bool is_art_restored_{};
41 |
42 | template
43 | static void InsertUnique(std::vector& vec, const T& value) {
44 | if (std::find(vec.begin(), vec.end(), value) == vec.end()) {
45 | vec.push_back(value);
46 | }
47 | }
48 |
49 | template
50 | static std::string FormatString(std::string_view fmt, Args... args) {
51 | std::array buffer{};
52 | snprintf(buffer.data(), buffer.size(), fmt.data(), args...);
53 | return buffer.data();
54 | }
55 |
56 | class XposedCallbackHelper {
57 | public:
58 | explicit XposedCallbackHelper(JNIEnv* env)
59 | : env_{env},
60 | class_cls_{JNI_FindClass(env, "java/lang/Class")},
61 | field_cls_{JNI_FindClass(env, "java/lang/reflect/Field")},
62 | collection_cls_{JNI_FindClass(env, "java/util/Collection")},
63 | key_set_view_cls_{JNI_FindClass(env, "java/util/concurrent/ConcurrentHashMap$KeySetView")},
64 | system_cls_{JNI_FindClass(env, "java/lang/System")},
65 | xposed_interface_cls_{env} {
66 | class_getDeclaredFields_ = JNI_GetMethodID(env_, class_cls_, "getDeclaredFields", "()[Ljava/lang/reflect/Field;");
67 | class_getName_ = JNI_GetMethodID(env_, class_cls_, "getName", "()Ljava/lang/String;");
68 | class_getSimpleName_ = JNI_GetMethodID(env_, class_cls_, "getSimpleName", "()Ljava/lang/String;");
69 | class_getInterfaces_ = JNI_GetMethodID(env_, class_cls_, "getInterfaces", "()[Ljava/lang/Class;");
70 |
71 | field_setAccessible_ = JNI_GetMethodID(env_, field_cls_, "setAccessible", "(Z)V");
72 | field_get_ = JNI_GetMethodID(env_, field_cls_, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
73 | field_getModifiers_ = JNI_GetMethodID(env_, field_cls_, "getModifiers", "()I");
74 |
75 | collection_clear_ = JNI_GetMethodID(env_, collection_cls_, "clear", "()V");
76 |
77 | auto iterable_cls = JNI_FindClass(env, "java/lang/Iterable");
78 | iterable_iterator_ = JNI_GetMethodID(env, iterable_cls, "iterator", "()Ljava/util/Iterator;");
79 |
80 | auto iterator_cls = JNI_FindClass(env, "java/util/Iterator");
81 | iterator_hasNext_ = JNI_GetMethodID(env, iterator_cls, "hasNext", "()Z");
82 | iterator_next_ = JNI_GetMethodID(env, iterator_cls, "next", "()Ljava/lang/Object;");
83 |
84 | system_identityHashCode_ = JNI_GetStaticMethodID(env, system_cls_, "identityHashCode", "(Ljava/lang/Object;)I");
85 | }
86 |
87 | void ClearXposedCallbacks(ScopedLocalRef& cls) {
88 | if (legacy_cleared_ && modern_cleared_) return;
89 |
90 | auto name_jstr = JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, cls, class_cls_, class_getSimpleName_));
91 | if (!name_jstr) return;
92 | auto name = JUTFString{name_jstr};
93 |
94 | if (!modern_cleared_) {
95 | if (name.get() == "XposedInterface"sv) {
96 | xposed_interface_cls_ = cls.clone();
97 | return;
98 | } else if (ClearModernCallbacks(cls)) {
99 | modern_cleared_ = true;
100 | return;
101 | }
102 | }
103 |
104 | if (!legacy_cleared_ && name.get() == "XposedBridge"sv) {
105 | ClearLegacyCallbacks(cls);
106 | legacy_cleared_ = true;
107 | }
108 | }
109 |
110 | private:
111 | void ClearLegacyCallbacks(ScopedLocalRef& cls) { ClearStaticFieldsAssignableTo(cls, collection_cls_, true); }
112 |
113 | bool ClearModernCallbacks(ScopedLocalRef& cls) {
114 | if (!key_set_view_cls_) return false;
115 |
116 | bool is_xposed = false;
117 |
118 | if (xposed_interface_cls_) {
119 | if (env_->IsAssignableFrom(cls.get(), xposed_interface_cls_.get())) {
120 | is_xposed = true;
121 | }
122 | } else {
123 | auto interfaces =
124 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, cls, class_cls_, class_getInterfaces_));
125 | if (!interfaces) return false;
126 |
127 | for (auto& interface : interfaces) {
128 | if (!interface.get()) continue;
129 |
130 | auto jstr =
131 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, interface, class_cls_, class_getSimpleName_));
132 | if (!jstr) continue;
133 | auto interface_name = JUTFString{jstr};
134 |
135 | if (interface_name.get() == "XposedInterface"sv) {
136 | xposed_interface_cls_.reset(reinterpret_cast(interface.release()));
137 | is_xposed = true;
138 | break;
139 | }
140 | }
141 | }
142 |
143 | if (is_xposed && ClearStaticFieldsAssignableTo(cls, key_set_view_cls_, false)) {
144 | if (auto framework_name = GetFrameworkName(cls); !framework_name.empty()) {
145 | framework_name_ = framework_name;
146 | }
147 | return true;
148 | }
149 | return false;
150 | }
151 |
152 | bool ClearStaticFieldsAssignableTo(ScopedLocalRef& cls,
153 | ScopedLocalRef& expected_type,
154 | bool has_wrapper) {
155 | if (!cls || !expected_type) return false;
156 |
157 | auto fields =
158 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, cls, class_cls_, class_getDeclaredFields_));
159 | if (!fields) return false;
160 |
161 | auto cleared = false;
162 |
163 | for (auto& field : fields) {
164 | JNI_CallNonvirtualVoidMethod(env_, field.get(), field_cls_, field_setAccessible_, JNI_TRUE);
165 | auto modifiers = JNI_CallNonvirtualIntMethod(env_, field.get(), field_cls_, field_getModifiers_);
166 | if (!(modifiers & kAccStatic)) continue;
167 |
168 | auto collection = JNI_CallNonvirtualObjectMethod(env_, field.get(), field_cls_, field_get_, nullptr);
169 | if (collection && JNI_IsInstanceOf(env_, collection, expected_type)) {
170 | cleared = true;
171 | auto iterator = JNI_CallObjectMethod(env_, collection, iterable_iterator_);
172 | while (JNI_CallBooleanMethod(env_, iterator, iterator_hasNext_)) {
173 | auto callback = JNI_CallObjectMethod(env_, iterator, iterator_next_);
174 | if (has_wrapper) {
175 | if (auto wrapped_callback = GetFirstNonNullInstanceField(callback)) {
176 | callback.reset(wrapped_callback);
177 | }
178 | }
179 | InsertUnique(cleared_callbacks_, GetObjectString(callback));
180 | }
181 | JNI_CallVoidMethod(env_, collection, collection_clear_);
182 | }
183 | }
184 |
185 | return cleared;
186 | }
187 |
188 | std::string GetFrameworkName(ScopedLocalRef& cls) {
189 | auto get_framework_name_mid = JNI_GetMethodID(env_, cls, "getFrameworkName", "()Ljava/lang/String;");
190 | if (!get_framework_name_mid) return {};
191 |
192 | auto get_framework_version_mid = JNI_GetMethodID(env_, cls, "getFrameworkVersion", "()Ljava/lang/String;");
193 | if (!get_framework_version_mid) return {};
194 |
195 | auto get_framework_version_code_mid = JNI_GetMethodID(env_, cls, "getFrameworkVersionCode", "()J");
196 | if (!get_framework_version_code_mid) return {};
197 |
198 | auto xposed_module = JNI_SafeInvoke(env_, &JNIEnv::AllocObject, cls);
199 | if (!xposed_module) return {};
200 |
201 | auto name_jstr =
202 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, xposed_module, cls, get_framework_name_mid));
203 | auto version_jstr =
204 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, xposed_module, cls, get_framework_version_mid));
205 | auto version_code = JNI_CallNonvirtualLongMethod(env_, xposed_module, cls, get_framework_version_code_mid);
206 |
207 | if (name_jstr && version_jstr) {
208 | auto name = JUTFString{name_jstr};
209 | auto version = JUTFString{version_jstr};
210 | return FormatString("%s %s (%lld)", name.get(), version.get(), static_cast(version_code));
211 | } else if (name_jstr) {
212 | auto name = JUTFString{name_jstr};
213 | return name.get();
214 | } else {
215 | return {};
216 | }
217 | }
218 |
219 | std::string GetObjectString(ScopedLocalRef& obj) {
220 | auto cls = JNI_GetObjectClass(env_, obj);
221 | auto class_name_jstr = JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, cls, class_cls_, class_getName_));
222 | auto class_name = JUTFString{class_name_jstr};
223 | auto hash_code = JNI_CallStaticIntMethod(env_, system_cls_, system_identityHashCode_, obj);
224 | return FormatString("%s@%x", class_name.get(), hash_code);
225 | }
226 |
227 | jobject GetFirstNonNullInstanceField(ScopedLocalRef& obj) {
228 | auto cls = JNI_GetObjectClass(env_, obj);
229 |
230 | auto fields =
231 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env_, cls, class_cls_, class_getDeclaredFields_));
232 | if (!fields) return nullptr;
233 |
234 | for (auto& field : fields) {
235 | JNI_CallNonvirtualVoidMethod(env_, field.get(), field_cls_, field_setAccessible_, JNI_TRUE);
236 | auto modifiers = JNI_CallNonvirtualIntMethod(env_, field.get(), field_cls_, field_getModifiers_);
237 | if (modifiers & kAccStatic) continue;
238 |
239 | auto instance = JNI_CallNonvirtualObjectMethod(env_, field.get(), field_cls_, field_get_, obj);
240 | if (instance) {
241 | return instance.release();
242 | }
243 | }
244 |
245 | return nullptr;
246 | }
247 |
248 | static constexpr jint kAccStatic = 0x0008;
249 |
250 | JNIEnv* env_;
251 |
252 | ScopedLocalRef class_cls_;
253 | ScopedLocalRef field_cls_;
254 | ScopedLocalRef collection_cls_;
255 | ScopedLocalRef key_set_view_cls_;
256 | ScopedLocalRef system_cls_;
257 |
258 | ScopedLocalRef xposed_interface_cls_;
259 |
260 | jmethodID class_getDeclaredFields_;
261 | jmethodID class_getName_;
262 | jmethodID class_getSimpleName_;
263 | jmethodID class_getInterfaces_;
264 | jmethodID field_setAccessible_;
265 | jmethodID field_get_;
266 | jmethodID field_getModifiers_;
267 | jmethodID collection_clear_;
268 | jmethodID iterable_iterator_;
269 | jmethodID iterator_hasNext_;
270 | jmethodID iterator_next_;
271 | jmethodID system_identityHashCode_;
272 |
273 | bool legacy_cleared_{false};
274 | bool modern_cleared_{false};
275 | };
276 |
277 | class Unsafe {
278 | public:
279 | explicit Unsafe(JNIEnv* env) : env_{env}, unsafe_{env, nullptr}, object_arr_{env, nullptr} {
280 | auto unsafe_cls = JNI_FindClass(env, "sun/misc/Unsafe");
281 | unsafe_ = JNI_SafeInvoke(env, &JNIEnv::AllocObject, unsafe_cls);
282 |
283 | object_arr_ = JNI_NewObjectArray(env, 1, JNI_FindClass(env, "java/lang/Object"), nullptr);
284 | auto array_base_offset_mid = JNI_GetMethodID(env, unsafe_cls, "arrayBaseOffset", "(Ljava/lang/Class;)I");
285 | auto object_arr_cls = JNI_GetObjectClass(env, object_arr_.get());
286 | object_arr_base_off_ = JNI_CallNonvirtualIntMethod(env, unsafe_, unsafe_cls, array_base_offset_mid, object_arr_cls);
287 | get_int_mid_ = JNI_GetMethodID(env, unsafe_cls, "getInt", "(Ljava/lang/Object;J)I");
288 | put_int_mid_ = JNI_GetMethodID(env, unsafe_cls, "putInt", "(Ljava/lang/Object;JI)V");
289 | }
290 |
291 | auto GetInt(jobject obj, jlong offset) { return JNI_CallIntMethod(env_, unsafe_, get_int_mid_, obj, offset); }
292 |
293 | void PutInt(jobject obj, jlong offset, jint x) { JNI_CallVoidMethod(env_, unsafe_, put_int_mid_, obj, offset, x); }
294 |
295 | auto GetObjectAddress(jobject obj) {
296 | object_arr_[0] = obj;
297 | return static_cast(GetInt(object_arr_.get(), object_arr_base_off_));
298 | }
299 |
300 | template
301 | auto NewLocalRef(uint32_t addr) {
302 | PutInt(object_arr_.get(), object_arr_base_off_, static_cast(addr));
303 | return reinterpret_cast(object_arr_[0].release());
304 | }
305 |
306 | private:
307 | JNIEnv* env_;
308 | ScopedLocalRef unsafe_;
309 | ScopedLocalRef object_arr_;
310 | jmethodID get_int_mid_;
311 | jmethodID put_int_mid_;
312 | jint object_arr_base_off_;
313 | };
314 |
315 | static auto GetClassNameList(JNIEnv* env,
316 | ScopedLocalRef& cls,
317 | jfieldID dex_cache_fid,
318 | jfieldID dex_file_fid,
319 | ScopedLocalRef& dex_file_cls,
320 | jmethodID get_class_name_list_mid) {
321 | auto dex_cache = JNI_GetObjectField(env, cls, dex_cache_fid);
322 | auto native_dex_file = JNI_GetLongField(env, dex_cache, dex_file_fid);
323 | auto cookie = JNI_NewLongArray(env, 2);
324 | cookie[1] = native_dex_file;
325 | cookie.commit();
326 | return JNI_Cast(JNI_CallStaticObjectMethod(env, dex_file_cls, get_class_name_list_mid, cookie));
327 | }
328 |
329 | static void* GetLSPEntryMethod(void* entry_point) {
330 | #if defined(__aarch64__)
331 | auto code = static_cast(entry_point);
332 | for (size_t i = 0; 8 > i; ++i) {
333 | if (code[i] != 0x58000060) continue;
334 | if ((code[i + 1] & 0xFFF00FFF) != 0xF8400010) continue;
335 | if (code[i + 2] != 0xD61F0200) continue;
336 | return *reinterpret_cast(&code[i + 3]);
337 | }
338 | #elif defined(__arm__)
339 | auto code = static_cast(entry_point);
340 | for (size_t i = 0; 8 > i; ++i) {
341 | if (code[i] != 0xE59F0000) continue;
342 | if ((code[i + 1] & 0xFFFFFF00) != 0xE590FF00) continue;
343 | return *reinterpret_cast(&code[i + 2]);
344 | }
345 | #elif defined(__i386__)
346 | auto code = static_cast(entry_point);
347 | for (size_t i = 0; 32 > i; ++i) {
348 | if (code[i] != 0xB8) continue;
349 | if (code[i + 5] != 0xFF) continue;
350 | if (code[i + 6] != 0x70) continue;
351 | if (code[i + 8] != 0xC3) continue;
352 | return *reinterpret_cast(&code[i + 1]);
353 | }
354 | #elif defined(__x86_64__)
355 | auto code = static_cast(entry_point);
356 | for (size_t i = 0; 32 > i; ++i) {
357 | if (code[i] != 0x48) continue;
358 | if (code[i + 1] != 0xBF) continue;
359 | if (code[i + 10] != 0xFF) continue;
360 | if (code[i + 11] != 0x77) continue;
361 | if (code[i + 13] != 0xC3) continue;
362 | return *reinterpret_cast(&code[i + 2]);
363 | }
364 | #elif defined(__riscv)
365 | auto code = static_cast(entry_point);
366 | for (size_t i = 0; 8 > i; ++i) {
367 | if (code[i] != 0x00000517) continue;
368 | if (code[i + 1] != 0x01053503) continue;
369 | if ((code[i + 2] & 0xF00FFFFF) != 0x00053F83) continue;
370 | if (code[i + 3] != 0x000F8067) continue;
371 | return *reinterpret_cast(&code[i + 4]);
372 | }
373 | #endif
374 | return nullptr;
375 | }
376 |
377 | static std::optional, ScopedLocalRef>>
378 | FindFrameworkAPIClassAndClassLoaderByHookedMethod(JNIEnv* env,
379 | ScopedLocalRef& class_cls,
380 | jmethodID get_class_loader_mid,
381 | jfieldID art_method_fid,
382 | size_t art_method_size,
383 | Unsafe& unsafe) {
384 | auto loaded_apk_cls = JNI_FindClass(env, "android/app/LoadedApk");
385 | auto hooked_mid = JNI_GetMethodID(env,
386 | loaded_apk_cls,
387 | "",
388 | "(Landroid/app/ActivityThread;Landroid/content/pm/ApplicationInfo;Landroid/content/"
389 | "res/CompatibilityInfo;Ljava/lang/ClassLoader;ZZZ)V");
390 | if (!hooked_mid) {
391 | hooked_mid = JNI_GetMethodID(env, loaded_apk_cls, "createOrUpdateClassLoaderLocked", "(Ljava/util/List;)V");
392 | if (!hooked_mid) return {};
393 | }
394 | auto hooked_method = JNI_ToReflectedMethod(env, loaded_apk_cls, hooked_mid, JNI_FALSE);
395 | auto art_method = JNI_GetLongField(env, hooked_method, art_method_fid);
396 | auto entry_point = *reinterpret_cast(static_cast(art_method) + art_method_size - sizeof(void*));
397 | void* hooker_art_method = GetLSPEntryMethod(entry_point);
398 | if (!hooker_art_method) return {};
399 | auto hooker_cls = unsafe.NewLocalRef(*static_cast(hooker_art_method));
400 | auto get_declared_fields_mid = JNI_GetMethodID(env, class_cls, "getDeclaredFields", "()[Ljava/lang/reflect/Field;");
401 | auto fields =
402 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env, hooker_cls, class_cls, get_declared_fields_mid));
403 | if (!fields || fields.size() != 1) return {};
404 | auto callback_fid = JNI_SafeInvoke(env, &JNIEnv::FromReflectedField, fields[0]);
405 | if (!callback_fid) return {};
406 | auto callback = JNI_GetStaticObjectField(env, hooker_cls, callback_fid);
407 | if (!callback) return {};
408 | auto callback_cls = JNI_GetObjectClass(env, callback);
409 | auto class_loader = JNI_CallNonvirtualObjectMethod(env, callback_cls, class_cls, get_class_loader_mid);
410 | return std::pair{std::move(callback_cls), std::move(class_loader)};
411 | }
412 |
413 | static std::optional, ScopedLocalRef>>
414 | FindFrameworkAPIClassAndClassLoaderByStackTrace(JNIEnv* env,
415 | ScopedLocalRef& class_cls,
416 | ScopedLocalRef& dex_file_cls,
417 | jmethodID get_class_loader_mid,
418 | jfieldID dex_cache_fid,
419 | jfieldID dex_file_fid,
420 | jmethodID get_class_name_list_mid) {
421 | auto load_dex_mid = JNI_GetStaticMethodID(
422 | env, dex_file_cls, "loadDex", "(Ljava/lang/String;Ljava/lang/String;I)Ldalvik/system/DexFile;");
423 | env->CallStaticObjectMethod(dex_file_cls.get(), load_dex_mid, nullptr, nullptr, jint{0});
424 | if (!env->ExceptionCheck()) {
425 | return {};
426 | }
427 | auto exception = ScopedLocalRef{env, env->ExceptionOccurred()};
428 | env->ExceptionClear();
429 |
430 | auto throwable_cls = JNI_FindClass(env, "java/lang/Throwable");
431 | auto backtrace_fid = JNI_GetFieldID(env, throwable_cls, "backtrace", "Ljava/lang/Object;");
432 | auto backtrace = JNI_Cast(JNI_GetObjectField(env, exception, backtrace_fid));
433 | auto boot_class_loader = JNI_CallNonvirtualObjectMethod(env, throwable_cls, class_cls, get_class_loader_mid);
434 | ScopedLocalRef framework_api_class{env};
435 | ScopedLocalRef framework_api_class_loader{env};
436 |
437 | for (jsize i = 2, len = static_cast(backtrace.size()); i < len; ++i) {
438 | auto element = JNI_Cast(backtrace[i]);
439 | if (!element) continue;
440 | auto class_loader = JNI_CallNonvirtualObjectMethod(env, element, class_cls, get_class_loader_mid);
441 | if (!class_loader) continue;
442 |
443 | if (JNI_IsSameObject(env, class_loader, boot_class_loader)) {
444 | continue;
445 | }
446 |
447 | if (!framework_api_class_loader) {
448 | framework_api_class.reset(element.release());
449 | framework_api_class_loader.reset(class_loader.release());
450 | continue;
451 | }
452 |
453 | if (JNI_IsSameObject(env, class_loader, framework_api_class_loader)) {
454 | framework_api_class.reset(element.release());
455 | continue;
456 | }
457 |
458 | auto class_names =
459 | GetClassNameList(env, element, dex_cache_fid, dex_file_fid, dex_file_cls, get_class_name_list_mid);
460 | if (class_names.size() == 1) {
461 | return std::pair{std::move(framework_api_class), std::move(framework_api_class_loader)};
462 | }
463 |
464 | framework_api_class.reset(element.release());
465 | framework_api_class_loader.reset(class_loader.release());
466 | }
467 |
468 | return std::nullopt;
469 | }
470 |
471 | static auto CollectIndirectRefTables() {
472 | static constexpr auto kTargetName = "[anon:dalvik-indirect ref table]"sv;
473 | [[maybe_unused]] static constexpr auto kNameOffset64 = 25 + sizeof(uint64_t) * 6;
474 |
475 | std::unordered_map tables;
476 |
477 | for (auto& line : io::FileReader{"/proc/self/maps"}) {
478 | #ifdef __LP64__
479 | if (line.size() <= kNameOffset64) continue;
480 | if (line.substr(kNameOffset64) != kTargetName) continue;
481 | #else
482 | if (line.find(kTargetName) == std::string_view::npos) continue;
483 | #endif
484 | char* tmp = nullptr;
485 | auto start = strtoul(line.data(), &tmp, 16);
486 | auto end = strtoul(tmp + 1 /* '-' */, nullptr, 16);
487 | tables.emplace(start, end);
488 | }
489 | return tables;
490 | }
491 |
492 | static std::optional> FindGlobalRefTable(JavaVM* vm) {
493 | auto indirect_ref_tables = CollectIndirectRefTables();
494 | if (indirect_ref_tables.empty()) {
495 | return {};
496 | }
497 |
498 | uint32_t* global_ref_table = nullptr;
499 | size_t global_ref_count = 0;
500 |
501 | auto mem = reinterpret_cast(vm + 1);
502 | for (size_t i = 0; i < 512; ++i) {
503 | if (mem[i + 1] != 2 /* IndirectRefKind::kGlobal */) continue;
504 | if (mem[i + 2] > 1'000'000) continue;
505 | if (mem[i + 3] > 1'000'000) continue;
506 |
507 | if (std::find_if(indirect_ref_tables.begin(), indirect_ref_tables.end(), [addr = mem[i]](const auto& p) {
508 | return p.first <= addr && addr < p.second;
509 | }) == indirect_ref_tables.end())
510 | continue;
511 |
512 | global_ref_table = reinterpret_cast(mem[i]);
513 | global_ref_count = mem[i + 2];
514 | break;
515 | }
516 |
517 | if (!global_ref_table) return {};
518 | return std::span{global_ref_table, global_ref_count};
519 | }
520 |
521 | static void RemapExecutableSegmentsForArt(JavaVM* vm) {
522 | Dl_info info{};
523 | if (!dladdr(reinterpret_cast(vm->functions->GetEnv), &info)) return;
524 |
525 | auto fd = raw_open(info.dli_fname, O_RDONLY, 0);
526 | if (fd < 0) return;
527 | auto size = raw_lseek(fd, 0, SEEK_END);
528 | auto elf = static_cast(raw_mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0));
529 | if (reinterpret_cast(elf) >= -4095UL) {
530 | raw_close(fd);
531 | return;
532 | }
533 |
534 | for (auto phdr = reinterpret_cast(reinterpret_cast(elf) + elf->e_phoff),
535 | phdr_limit = phdr + elf->e_phnum;
536 | phdr < phdr_limit;
537 | ++phdr) {
538 | if (phdr->p_type != PT_LOAD) continue;
539 | if ((phdr->p_flags & PF_X) == 0) continue;
540 | if ((phdr->p_flags & PF_W) != 0) continue;
541 |
542 | auto segment_addr = __builtin_align_down(static_cast(info.dli_fbase) + phdr->p_vaddr, phdr->p_align);
543 | auto segment_size = __builtin_align_up(phdr->p_memsz, static_cast(getpagesize()));
544 | auto segment_offset = __builtin_align_down(static_cast(phdr->p_offset), phdr->p_align);
545 |
546 | auto segment_prot = PROT_EXEC;
547 | if (phdr->p_flags & PF_R) segment_prot |= PROT_READ;
548 |
549 | auto map = raw_mmap(segment_addr, segment_size, segment_prot, MAP_PRIVATE | MAP_FIXED, fd, segment_offset);
550 | if (reinterpret_cast(map) >= -4095UL) {
551 | continue;
552 | }
553 | __builtin___clear_cache(segment_addr, segment_addr + segment_size);
554 | is_art_restored_ = true;
555 | }
556 |
557 | raw_munmap(elf, size);
558 | raw_close(fd);
559 | }
560 |
561 | static jobjectArray ToStringArray(JNIEnv* env, std::vector& vec) {
562 | auto string_cls = JNI_FindClass(env, "java/lang/String");
563 | auto arr = JNI_NewObjectArray(env, static_cast(vec.size()), string_cls, nullptr);
564 | jsize i = 0;
565 | for (const auto& s : vec) {
566 | auto jstr = JNI_NewStringUTF(env, s);
567 | arr[i++] = jstr.get();
568 | }
569 | vec.clear();
570 | return arr.release();
571 | }
572 |
573 | extern "C" {
574 | JNIEXPORT jobjectArray Java_io_github_eirv_disablelsposed_Native_nGetUnhookedMethods([[maybe_unused]] JNIEnv* env,
575 | jclass) {
576 | #ifdef USE_SPANNABLE_STRING_BUILDER
577 | return nullptr;
578 | #else
579 | return ToStringArray(env, unhooked_methods_);
580 | #endif
581 | }
582 |
583 | JNIEXPORT jobject Java_io_github_eirv_disablelsposed_Native_nGetUnhookedMethodList([[maybe_unused]] JNIEnv* env,
584 | jclass) {
585 | #ifdef USE_SPANNABLE_STRING_BUILDER
586 | if (!unhooked_method_list_) return nullptr;
587 | auto list = env->NewLocalRef(unhooked_method_list_);
588 | env->DeleteGlobalRef(unhooked_method_list_);
589 | unhooked_method_list_ = nullptr;
590 | return list;
591 | #else
592 | return nullptr;
593 | #endif
594 | }
595 |
596 | JNIEXPORT jstring Java_io_github_eirv_disablelsposed_Native_nGetFrameworkName(JNIEnv* env, jclass) {
597 | return env->NewStringUTF(framework_name_.c_str());
598 | }
599 |
600 | JNIEXPORT jobjectArray Java_io_github_eirv_disablelsposed_Native_nGetClearedCallbacks(JNIEnv* env, jclass) {
601 | return ToStringArray(env, cleared_callbacks_);
602 | }
603 |
604 | JNIEXPORT jint Java_io_github_eirv_disablelsposed_Native_nGetFlags(JNIEnv*, jclass) {
605 | jint flags = 0;
606 | if (is_lsposed_disabled_) flags |= 1 << 0;
607 | if (is_art_restored_) flags |= 1 << 1;
608 | return flags;
609 | }
610 | }
611 |
612 | jint JNI_OnLoad(JavaVM* vm, void*) {
613 | JNIEnv* env;
614 | if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) {
615 | return JNI_ERR;
616 | }
617 |
618 | Unsafe unsafe{env};
619 |
620 | auto method_cls = JNI_FindClass(env, "java/lang/reflect/Method");
621 | auto class_cls = JNI_GetObjectClass(env, method_cls);
622 | auto method_get_name_mid = JNI_GetMethodID(env, method_cls, "getName", "()Ljava/lang/String;");
623 | #ifdef USE_SPANNABLE_STRING_BUILDER
624 | auto method_get_parameter_types_mid = JNI_GetMethodID(env, method_cls, "getParameterTypes", "()[Ljava/lang/Class;");
625 | auto method_get_return_type_mid = JNI_GetMethodID(env, method_cls, "getReturnType", "()Ljava/lang/Class;");
626 | #else
627 | auto class_get_name_mid = JNI_GetMethodID(env, class_cls, "getName", "()Ljava/lang/String;");
628 | #endif
629 | auto executable_cls = JNI_SafeInvoke(env, &JNIEnv::GetSuperclass, method_cls);
630 |
631 | auto declaring_class_fid = JNI_GetFieldID(env, executable_cls, "declaringClass", "Ljava/lang/Class;");
632 | auto declaring_class_field = JNI_ToReflectedField(env, executable_cls, declaring_class_fid, JNI_FALSE);
633 | auto field_cls = JNI_GetObjectClass(env, declaring_class_field);
634 | auto offset_fid = JNI_GetFieldID(env, field_cls, "offset", "I");
635 | auto declaring_class_off = JNI_GetIntField(env, declaring_class_field, offset_fid);
636 |
637 | auto art_method_fid = JNI_GetFieldID(env, executable_cls, "artMethod", "J");
638 | auto art_method_field = JNI_ToReflectedField(env, executable_cls, art_method_fid, JNI_FALSE);
639 | auto art_method_off = JNI_GetIntField(env, art_method_field, offset_fid);
640 |
641 | auto methods_fid = JNI_GetFieldID(env, class_cls, "methods", "J");
642 | auto methods_field = JNI_ToReflectedField(env, class_cls, methods_fid, JNI_FALSE);
643 | auto methods_off = JNI_GetIntField(env, methods_field, offset_fid);
644 |
645 | auto method_cls_addr = unsafe.GetObjectAddress(method_cls.get());
646 |
647 | size_t art_method_size = 0;
648 | {
649 | auto methods = static_cast(*reinterpret_cast(method_cls_addr + methods_off));
650 | auto art_method = reinterpret_cast(methods + sizeof(size_t));
651 | for (size_t i = 5; i < 32; ++i) {
652 | if (art_method[i] != method_cls_addr) continue;
653 | art_method_size = i * sizeof(uint32_t);
654 | break;
655 | }
656 | }
657 |
658 | auto dex_file_cls = JNI_FindClass(env, "dalvik/system/DexFile");
659 | auto get_class_loader_mid = JNI_GetMethodID(env, class_cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
660 | auto dex_cache_cls = JNI_FindClass(env, "java/lang/DexCache");
661 | auto dex_cache_fid = JNI_GetFieldID(env, class_cls, "dexCache", "Ljava/lang/Object;");
662 | auto dex_file_fid = JNI_GetFieldID(env, dex_cache_cls, "dexFile", "J");
663 | auto get_class_name_list_mid =
664 | JNI_GetStaticMethodID(env, dex_file_cls, "getClassNameList", "(Ljava/lang/Object;)[Ljava/lang/String;");
665 |
666 | auto maybe_framework_api =
667 | FindFrameworkAPIClassAndClassLoaderByHookedMethod(env, class_cls, get_class_loader_mid, art_method_fid, art_method_size, unsafe) ?:
668 | FindFrameworkAPIClassAndClassLoaderByStackTrace(
669 | env, class_cls, dex_file_cls, get_class_loader_mid, dex_cache_fid, dex_file_fid, get_class_name_list_mid);
670 |
671 | if (maybe_framework_api) {
672 | auto& [framework_api_class, framework_api_class_loader] = *maybe_framework_api;
673 | XposedCallbackHelper helper{env};
674 |
675 | auto for_name_mid = JNI_GetStaticMethodID(
676 | env, class_cls, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
677 |
678 | auto names =
679 | GetClassNameList(env, framework_api_class, dex_cache_fid, dex_file_fid, dex_file_cls, get_class_name_list_mid);
680 |
681 | auto native_methods = std::array{JNINativeMethod{
682 | "hookMethod",
683 | "(ZLjava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z",
684 | reinterpret_cast(
685 | +[](JNIEnv*, jclass, jboolean, jobject, jobject, jint, jobject) -> jboolean { return JNI_TRUE; })}};
686 |
687 | for (auto& name : names) {
688 | auto current_class = JNI_Cast(
689 | JNI_CallStaticObjectMethod(env, class_cls, for_name_mid, name, JNI_FALSE, framework_api_class_loader));
690 | if (!current_class) {
691 | continue;
692 | }
693 | helper.ClearXposedCallbacks(current_class);
694 |
695 | if (!is_lsposed_disabled_) {
696 | if (env->GetStaticMethodID(current_class.get(), native_methods[0].name, native_methods[0].signature) ==
697 | nullptr) {
698 | env->ExceptionClear();
699 | continue;
700 | }
701 | if (JNI_RegisterNatives(env, current_class, native_methods.data(), native_methods.size()) != JNI_OK) {
702 | continue;
703 | }
704 | is_lsposed_disabled_ = true;
705 | }
706 | }
707 | }
708 |
709 | #ifdef USE_SPANNABLE_STRING_BUILDER
710 | auto array_list_cls = JNI_FindClass(env, "java/util/ArrayList");
711 | auto array_list_init_mid = JNI_GetMethodID(env, array_list_cls, "", "()V");
712 | auto array_list_add_mid = JNI_GetMethodID(env, array_list_cls, "add", "(Ljava/lang/Object;)Z");
713 | auto array_list = JNI_NewGlobalRef(env, JNI_NewObject(env, array_list_cls, array_list_init_mid));
714 | unhooked_method_list_ = array_list;
715 | DescriptorBuilder db{env};
716 | #endif
717 |
718 | if (auto global_ref_table = FindGlobalRefTable(vm)) {
719 | auto compiler_cls = JNI_FindClass(env, "java/lang/Compiler");
720 | auto enable_mid = JNI_GetStaticMethodID(env, compiler_cls, "enable", "()V");
721 | auto stub_method = JNI_ToReflectedMethod(env, compiler_cls, enable_mid, JNI_TRUE);
722 |
723 | for (size_t i = 0; i < global_ref_table->size(); ++i) {
724 | auto ref = global_ref_table->data()[i * 2 + 1];
725 | if (ref == 0) continue;
726 | auto ref_class_addr = *reinterpret_cast(ref);
727 | if (ref_class_addr != method_cls_addr) continue;
728 |
729 | auto art_method = static_cast(*reinterpret_cast(ref + art_method_off));
730 | auto target_class_addr = *reinterpret_cast(art_method);
731 | auto declaring_class_addr = *reinterpret_cast(ref + declaring_class_off);
732 | if (target_class_addr == declaring_class_addr) continue;
733 |
734 | auto methods = static_cast(*reinterpret_cast(target_class_addr + methods_off));
735 | auto method_count = *reinterpret_cast(methods);
736 |
737 | for (size_t j = 0; j < method_count; ++j) {
738 | auto method = reinterpret_cast(j * art_method_size + methods + sizeof(size_t));
739 | if (method[2] != reinterpret_cast(art_method)[2]) continue;
740 |
741 | auto access_flags = method[1];
742 | if (!GetLSPEntryMethod(*reinterpret_cast(art_method + art_method_size - sizeof(void*)))) {
743 | memcpy(method, reinterpret_cast(art_method), art_method_size);
744 | method[1] = access_flags;
745 | } else {
746 | access_flags |= 0x1000 /* kAccSynthetic */;
747 | }
748 |
749 | auto target_cls = ScopedLocalRef{env, unsafe.NewLocalRef(target_class_addr)};
750 | JNI_SetLongField(env, stub_method, art_method_fid, reinterpret_cast(method));
751 | auto target_method_name_jstr =
752 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env, stub_method, method_cls, method_get_name_mid));
753 |
754 | #ifdef USE_SPANNABLE_STRING_BUILDER
755 | auto target_parameter_types =
756 | JNI_CallNonvirtualObjectMethod(env, stub_method, method_cls, method_get_parameter_types_mid);
757 | auto target_return_type =
758 | JNI_CallNonvirtualObjectMethod(env, stub_method, method_cls, method_get_return_type_mid);
759 | auto descriptor = DescriptorBuilder::GetDescriptor(env,
760 | target_cls.get(),
761 | target_method_name_jstr.get(),
762 | reinterpret_cast(target_parameter_types.get()),
763 | reinterpret_cast(target_return_type.get()),
764 | static_cast(access_flags));
765 | JNI_CallNonvirtualBooleanMethod(env, array_list, array_list_cls, array_list_add_mid, descriptor);
766 | #else
767 | auto target_cls_name_jstr =
768 | JNI_Cast(JNI_CallNonvirtualObjectMethod(env, target_cls, class_cls, class_get_name_mid));
769 | auto target_cls_name = JUTFString{target_cls_name_jstr};
770 | auto target_method_name = JUTFString{target_method_name_jstr};
771 | unhooked_methods_.push_back(target_cls_name.get() + "::"s + target_method_name.get());
772 | #endif
773 | break;
774 | }
775 | }
776 | }
777 |
778 | RemapExecutableSegmentsForArt(vm);
779 |
780 | return JNI_VERSION_1_6;
781 | }
782 |
--------------------------------------------------------------------------------
/app/src/main/cpp/third-party/lsplant/jni_helper.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | #include "type_traits.hpp"
10 |
11 | #pragma clang diagnostic push
12 | #pragma clang diagnostic ignored "-Winvalid-partial-specialization"
13 | #pragma clang diagnostic ignored "-Wunknown-pragmas"
14 | #pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
15 |
16 | #define DISALLOW_COPY_AND_ASSIGN(TypeName) \
17 | TypeName(const TypeName &) = delete; \
18 | void operator=(const TypeName &) = delete
19 |
20 | namespace lsplant {
21 | template
22 | concept JObject = std::is_base_of_v, std::remove_pointer_t>;
23 |
24 | template
25 | class ScopedLocalRef {
26 | public:
27 | using BaseType [[maybe_unused]] = T;
28 |
29 | ScopedLocalRef(JNIEnv *env, T local_ref) : env_(env), local_ref_(nullptr) { reset(local_ref); }
30 |
31 | ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, s.release()) {}
32 |
33 | template
34 | ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}
35 |
36 | explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{nullptr}) {}
37 |
38 | ~ScopedLocalRef() { reset(); }
39 |
40 | void reset(T ptr = nullptr) {
41 | if (ptr != local_ref_) {
42 | if (local_ref_ != nullptr) {
43 | env_->DeleteLocalRef(local_ref_);
44 | }
45 | local_ref_ = ptr;
46 | }
47 | }
48 |
49 | [[nodiscard]] T release() {
50 | T localRef = local_ref_;
51 | local_ref_ = nullptr;
52 | return localRef;
53 | }
54 |
55 | T get() const { return local_ref_; }
56 |
57 | ScopedLocalRef clone() const {
58 | return ScopedLocalRef(env_, (T)env_->NewLocalRef(local_ref_));
59 | }
60 |
61 | ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
62 | reset(s.release());
63 | env_ = s.env_;
64 | return *this;
65 | }
66 |
67 | operator bool() const { return local_ref_; }
68 |
69 | template
70 | friend class ScopedLocalRef;
71 |
72 | friend class JUTFString;
73 |
74 | private:
75 | JNIEnv *env_;
76 | T local_ref_;
77 | DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
78 | };
79 |
80 | class JObjectArrayElement;
81 |
82 | template
83 | concept JArray = std::is_base_of_v, std::remove_pointer_t>;
84 |
85 | template
86 | class ScopedLocalRef;
87 |
88 | class JNIScopeFrame {
89 | JNIEnv *env_;
90 |
91 | DISALLOW_COPY_AND_ASSIGN(JNIScopeFrame);
92 |
93 | public:
94 | JNIScopeFrame(JNIEnv *env, jint size) : env_(env) { env_->PushLocalFrame(size); }
95 |
96 | ~JNIScopeFrame() { env_->PopLocalFrame(nullptr); }
97 | };
98 |
99 | class JNIMonitor {
100 | JNIEnv *env_;
101 | jobject obj_;
102 |
103 | DISALLOW_COPY_AND_ASSIGN(JNIMonitor);
104 |
105 | public:
106 | JNIMonitor(JNIEnv *env, jobject obj) : env_(env), obj_(obj) { env_->MonitorEnter(obj_); }
107 |
108 | ~JNIMonitor() { env_->MonitorExit(obj_); }
109 | };
110 |
111 | template
112 | concept ScopeOrRaw =
113 | std::is_convertible_v ||
114 | (is_instance_v, ScopedLocalRef> &&
115 | std::is_convertible_v::BaseType, U>) ||
116 | (std::is_same_v, JObjectArrayElement> && std::is_convertible_v);
117 |
118 | template
119 | concept ScopeOrClass = ScopeOrRaw;
120 |
121 | template
122 | concept ScopeOrObject = ScopeOrRaw;
123 |
124 | inline ScopedLocalRef ClearException(JNIEnv *env) {
125 | if (auto exception = env->ExceptionOccurred()) {
126 | env->ExceptionClear();
127 | jclass log = (jclass)env->FindClass("android/util/Log");
128 | static jmethodID toString = env->GetStaticMethodID(
129 | log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
130 | auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);
131 | env->DeleteLocalRef(log);
132 | env->DeleteLocalRef(exception);
133 | return {env, str};
134 | }
135 | return {env, nullptr};
136 | }
137 |
138 | template
139 | [[maybe_unused]] inline auto UnwrapScope(T &&x) {
140 | if constexpr (std::is_same_v, std::string_view>)
141 | return x.data();
142 | else if constexpr (is_instance_v, ScopedLocalRef>)
143 | return x.get();
144 | else if constexpr (std::is_same_v, JObjectArrayElement>)
145 | return x.get();
146 | else
147 | return std::forward(x);
148 | }
149 |
150 | template
151 | [[maybe_unused]] inline auto WrapScope(JNIEnv *env, T &&x) {
152 | if constexpr (std::is_convertible_v) {
153 | return ScopedLocalRef(env, std::forward(x));
154 | } else
155 | return x;
156 | }
157 |
158 | template
159 | [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple &&x,
160 | std::index_sequence) {
161 | return std::make_tuple(WrapScope(env, std::forward(std::get(x)))...);
162 | }
163 |
164 | template
165 | [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple &&x) {
166 | return WrapScope(env, std::forward>(x),
167 | std::make_index_sequence());
168 | }
169 |
170 | inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
171 | return ScopedLocalRef(env, env->NewStringUTF(sv.data()));
172 | }
173 |
174 | class JUTFString {
175 | public:
176 | JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
177 |
178 | JUTFString(const ScopedLocalRef &jstr)
179 | : JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
180 |
181 | JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) {
182 | if (env_ && jstr_)
183 | cstr_ = env_->GetStringUTFChars(jstr, nullptr);
184 | else
185 | cstr_ = default_cstr;
186 | }
187 |
188 | operator const char *() const { return cstr_; }
189 |
190 | operator const std::string() const { return cstr_; }
191 |
192 | operator const bool() const { return cstr_ != nullptr; }
193 |
194 | auto get() const { return cstr_; }
195 |
196 | ~JUTFString() {
197 | if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
198 | }
199 |
200 | JUTFString(JUTFString &&other)
201 | : env_(std::move(other.env_)),
202 | jstr_(std::move(other.jstr_)),
203 | cstr_(std::move(other.cstr_)) {
204 | other.cstr_ = nullptr;
205 | }
206 |
207 | JUTFString &operator=(JUTFString &&other) {
208 | if (&other != this) {
209 | env_ = std::move(other.env_);
210 | jstr_ = std::move(other.jstr_);
211 | cstr_ = std::move(other.cstr_);
212 | other.cstr_ = nullptr;
213 | }
214 | return *this;
215 | }
216 |
217 | private:
218 | JNIEnv *env_;
219 | jstring jstr_;
220 | const char *cstr_;
221 |
222 | JUTFString(const JUTFString &) = delete;
223 |
224 | JUTFString &operator=(const JUTFString &) = delete;
225 | };
226 |
227 | template
228 | requires(std::is_function_v)
229 | [[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) {
230 | struct finally {
231 | finally(JNIEnv *env) : env_(env) {}
232 |
233 | ~finally() {
234 | if (auto exception = ClearException(env_)) {
235 | __android_log_print(ANDROID_LOG_ERROR,
236 | #ifdef LOG_TAG
237 | LOG_TAG,
238 | #else
239 | "JNIHelper",
240 | #endif
241 | "%s", JUTFString(env_, exception.get()).get());
242 | }
243 | }
244 |
245 | JNIEnv *env_;
246 | } _(env);
247 |
248 | if constexpr (!std::is_same_v(args)))...>>)
251 | return WrapScope(env, (env->*f)(UnwrapScope(std::forward(args))...));
252 | else
253 | (env->*f)(UnwrapScope(std::forward(args))...);
254 | }
255 |
256 | // functions to class
257 |
258 | [[maybe_unused]] inline auto JNI_FindClass(JNIEnv *env, std::string_view name) {
259 | return JNI_SafeInvoke(env, &JNIEnv::FindClass, name);
260 | }
261 |
262 | template
263 | [[maybe_unused]] inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) {
264 | return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj);
265 | }
266 |
267 | // functions to field
268 |
269 | template
270 | [[maybe_unused]] inline auto JNI_GetFieldID(JNIEnv *env, Class &&clazz, std::string_view name,
271 | std::string_view sig) {
272 | return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, std::forward