[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n"
  },
  {
    "path": ".idea/.gitignore",
    "content": "# Default ignored files\n/shelf/\n/workspace.xml\n"
  },
  {
    "path": ".idea/compiler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <bytecodeTargetLevel target=\"11\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/gradle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleMigrationSettings\" migrationVersion=\"1\" />\n  <component name=\"GradleSettings\">\n    <option name=\"linkedExternalProjectsSettings\">\n      <GradleProjectSettings>\n        <option name=\"testRunner\" value=\"GRADLE\" />\n        <option name=\"distributionType\" value=\"DEFAULT_WRAPPED\" />\n        <option name=\"externalProjectPath\" value=\"$PROJECT_DIR$\" />\n        <option name=\"modules\">\n          <set>\n            <option value=\"$PROJECT_DIR$\" />\n            <option value=\"$PROJECT_DIR$/app\" />\n            <option value=\"$PROJECT_DIR$/lib_sillyboy\" />\n          </set>\n        </option>\n      </GradleProjectSettings>\n    </option>\n  </component>\n</project>"
  },
  {
    "path": ".idea/misc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ASMIdeaPluginConfiguration\">\n    <asm skipDebug=\"false\" skipFrames=\"false\" skipCode=\"false\" expandFrames=\"false\" />\n    <groovy codeStyle=\"LEGACY\" />\n  </component>\n  <component name=\"ProjectRootManager\" version=\"2\" languageLevel=\"JDK_11\" default=\"true\" project-jdk-name=\"Android Studio default JDK\" project-jdk-type=\"JavaSDK\">\n    <output url=\"file://$PROJECT_DIR$/build/classes\" />\n  </component>\n  <component name=\"ProjectType\">\n    <option name=\"id\" value=\"Android\" />\n  </component>\n</project>"
  },
  {
    "path": ".idea/vcs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping directory=\"$PROJECT_DIR$\" vcs=\"Git\" />\n  </component>\n</project>"
  },
  {
    "path": "README.md",
    "content": "# dyso\nso dynamically loading （dy so）\nandroid 动态加载so库实现 你所不知道的“船新”版本\n## 关于demo\n本项目是动态so加载的核心流程，所以并不直接演示so的下载过程，需要demo使用者将test_so_package的三个demo so库放到指定的演示位置（模拟so已经下载好了，可对接自己使用的下载方式），本例子是放在目录\n```\ndata/data/com.example.sillyboy/files/dynamic_so\n```\n![image](https://user-images.githubusercontent.com/65278264/235083927-1a23914d-44da-493f-88ab-1b3dfa99326d.png)\n\n\n## 设计原理\n文档请看这里：https://juejin.cn/post/7107958280097366030\n\n## 使用说明\n### 声明\n注意：由于这是一个基础库，如果需要在正式项目中使用，还是需要继续封装或者优化的，这里只是动态加载so的实现，在实战中应当配合so版本管理，下载器等实际使用\n### 使用步骤\n1. 如何删除本地的so库【可选】\n```\n方式1\npackagingOptions下增加\nexclude 'lib/arm64-v8a/xxx.so'\nexclude 'lib/armeabi-v7a/xxx.so'\n```\n\n```\n方式2\n复制以下task到app的build.gradle\n\n如果需要删除so的过程中进行一些列的定制化操作，可参考如下task，见app目录例子\next {\n    deleteSoName = [\"libnativecpptwo.so\",\"libnativecpp.so\"]\n}\n// 这个是初始化 -配置 -执行阶段中，配置阶段执行的任务之一，完成afterEvaluate就可以得到所有的tasks，从而可以在里面插入我们定制化的数据\ntask(dynamicSo) {\n}.doLast {\n    println(\"dynamicSo insert!!!! \")\n    //projectDir 在哪个project下面，projectDir就是哪个路径\n    print(getRootProject().findAll())\n\n    def file = new File(\"${projectDir}/build/intermediates/merged_native_libs/debug/out/lib\")\n    //默认删除所有的so库\n    if (file.exists()) {\n        file.listFiles().each {\n            if (it.isDirectory()) {\n                it.listFiles().each {\n                    target ->\n                        print(\"file ${target.name}\")\n                        def compareName = target.name\n                        deleteSoName.each {\n                            if (compareName.contains(it)) {\n                                target.delete()\n                            }\n                        }\n                }\n            }\n        }\n    } else {\n        print(\"nil\")\n    }\n}\nafterEvaluate {\n    print(\"dynamicSo task start\")\n    def customer = tasks.findByName(\"dynamicSo\")\n    def merge = tasks.findByName(\"mergeDebugNativeLibs\")\n    def strip = tasks.findByName(\"stripDebugDebugSymbols\")\n    if (merge != null || strip != null) {\n        customer.mustRunAfter(merge)\n        strip.dependsOn(customer)\n    }\n\n}\n```\n2. 初始化\n\n通过initDynamicSoConfig 方法初始化，第一个参数是context，第二个参数是path，即下载完的so的path，第三个参数是一个回调，在调用动态so加载的时候会先回调，如果返回值为true，才会真正进入到so的加载逻辑，提供给使用者版本校验等一些列活动\n```\n    // 在合适的时候将自定义路径插入so检索路径 需要使用者自己负责在这个路径上有写入权限\n        DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> {\n            // 处理一些自定义逻辑\n            return true;\n        });\n```\n3. 调用so加载【有两种使用姿势】\n\n3.1 手动加载\n\n在使用到某个so方法前，需要调用loadSoDynamically（代替System.loadLibrary方法），参数是so的名称或者一个File，该File位于初始化时传入的path之内即可\n```\nDynamicSoLauncher.INSTANCE.loadSoDynamically(file)\n```\n\n3.2 注解加载\n\n在需要采取动态so加载的类上，添加@DynamicLoad注解即可，内部会采用字节码插桩的方式，会把类中所有的System.loadLibrary 替换为DynamicSoLauncher内部的so加载\n```\n//@DynamicLoad\npublic class MainActivity extends AppCompatActivity {\n```\n注意，执行这个步骤需要发布当前的lib_sillyplugin插件\n![image](https://user-images.githubusercontent.com/65278264/235086729-fa92051d-4282-4ffe-9817-d04de2587711.png)\n之后在需要的工程build.gradle引入即可，跟一般的插件引入方式一样\n```\napply plugin: 'com.plugins.core'\n```\nps：这里其实已经可以实现了无痕替代，只是考虑到大多数可能只动态加载少数的so，才提供出来注解的方式去限定范围。\n\n\n## 项目层级介绍\n* **app下是使用例子**\n* **lib_sillyboy 是dyso的封装实现**\n* **lib_sillyplugin 是dyso的插件，用于替换代码的System.loadLibrary,默认关闭了，可看代码注释打开**\n\n## 环境准备\n建议直接用最新的稳定版本Android Studio打开工程。目前项目已适配`Android Studio Arctic Fox | 2020.3.1`\n### \n\n\n## 后续todo\n* **maven发布**\n* **贡献者名单**\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/build.gradle",
    "content": "\napply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\n// 替换System.loadLibrary的插件\n//apply plugin: 'com.plugins.core'\nandroid {\n    compileSdkVersion 31\n\n    packagingOptions {\n        exclude 'META-INF/DEPENDENCIES'\n        exclude 'migrateToAndroidx/migration.xml'\n        exclude 'META-INF/common.kotlin_module'\n    }\n\n    defaultConfig {\n        applicationId \"com.example.sillyboy\"\n        minSdkVersion 23\n        targetSdkVersion 29\n        versionCode 1\n        versionName \"1.0\"\n\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n    kotlinOptions {\n        jvmTarget = '1.8'\n    }\n    viewBinding {\n        enabled = true\n    }\n}\n\n\next {\n    deleteSoName = [\"libnative1.so\",\"libnative2.so\",\"libnative3.so\"]\n}\n// 这个是初始化 -配置 -执行阶段中，配置阶段执行的任务之一，完成afterEvaluate就可以得到所有的tasks，从而可以在里面插入我们定制化的数据\ntask(dynamicSo) {\n}.doLast {\n    println(\"dynamicSo insert!!!! \")\n    //projectDir 在哪个project下面，projectDir就是哪个路径\n    print(getRootProject().findAll())\n\n    def file = new File(\"${projectDir}/build/intermediates/merged_native_libs/debug/out/lib\")\n    //默认删除所有的so库\n    if (file.exists()) {\n        file.listFiles().each {\n            if (it.isDirectory()) {\n                it.listFiles().each {\n                    target ->\n                        def compareName = target.name\n                        deleteSoName.each {\n                            if (compareName.contains(it)) {\n                                target.delete()\n                                print(\"delete file ${target.name}\")\n                            }\n                        }\n                }\n            }\n        }\n    } else {\n        print(\"nil\")\n    }\n}\nafterEvaluate {\n    print(\"dynamicSo task start\")\n    def customer = tasks.findByName(\"dynamicSo\")\n    def merge = tasks.findByName(\"mergeDebugNativeLibs\")\n    def strip = tasks.findByName(\"stripDebugDebugSymbols\")\n    if (merge != null || strip != null) {\n        customer.mustRunAfter(merge)\n        strip.dependsOn(customer)\n    }\n\n}\n\n\n\ndependencies {\n    implementation 'androidx.appcompat:appcompat:1.3.0'\n    implementation 'com.google.android.material:material:1.4.0'\n    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'\n    implementation 'androidx.core:core-ktx:1.5.0'\n    implementation project(path: ':lib_sillyboy')\n\n}\n\n\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "app/src/androidTest/java/com/example/nativecpp/ExampleInstrumentedTest.java",
    "content": "package com.example.nativecpp;\n\nimport android.content.Context;\n\nimport androidx.test.platform.app.InstrumentationRegistry;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();\n        assertEquals(\"com.example.nativecpp\", appContext.getPackageName());\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.nativecpp\">\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:name=\".CustomApplication\"\n        android:theme=\"@style/Theme.NativeCpp\">\n        <activity\n            android:name=\".MainActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/cpp/CMakeLists.txt",
    "content": "# For more information about using CMake with Android Studio, read the\n# documentation: https://d.android.com/studio/projects/add-native-code.html\n\n# Sets the minimum version of CMake required to build the native library.\n\ncmake_minimum_required(VERSION 3.18.1)\n\n# Declares and names the project.\n\nproject(\"nativecpp\")\n\n# Creates and names a library, sets it as either STATIC\n# or SHARED, and provides the relative paths to its source code.\n# You can define multiple libraries, and CMake builds them for you.\n# Gradle automatically packages shared libraries with your APK.\n\n\nadd_library(\n        native1\n        SHARED\n        nativeso1.c\n)\nadd_library(\n        native2\n        SHARED\n        nativeso2.c\n)\nadd_library(\n        native3\n        SHARED\n        nativeso3.c\n)\n\n# Searches for a specified prebuilt library and stores the path as a\n# variable. Because CMake includes system libraries in the search path by\n# default, you only need to specify the name of the public NDK library\n# you want to add. CMake verifies that the library exists before\n# completing its build.\n\nfind_library( # Sets the name of the path variable.\n        log-lib\n\n        # Specifies the name of the NDK library that\n        # you want CMake to locate.\n        log)\n\n# Specifies libraries CMake should link to your target library. You\n# can link multiple libraries, such as libraries you define in this\n# build script, prebuilt third-party libraries, or system libraries.\n\n# 构建多依赖场景\ntarget_link_libraries( # Specifies the target library.\n        native1\n\n        # Links the target library to the log library\n        # included in the NDK.\n        ${log-lib})\n\ntarget_link_libraries( # Specifies the target library.\n        native2\n\n        # Links the target library to the log library\n        # included in the NDK.\n        ${log-lib}\n        native1)\n\ntarget_link_libraries( # Specifies the target library.\n        native3\n\n        # Links the target library to the log library\n        # included in the NDK.\n        ${log-lib}\n        native2)\n\n"
  },
  {
    "path": "app/src/main/cpp/nativeso1.c",
    "content": "#include <jni.h>\n#include <android/log.h>\n\nJNIEXPORT void JNICALL\nJava_com_example_nativecpp_MainActivity_clickNative1(JNIEnv *env, jobject thiz) {\n    __android_log_print(ANDROID_LOG_ERROR, \"hello\", \"%s\", \"native1\");\n}"
  },
  {
    "path": "app/src/main/cpp/nativeso2.c",
    "content": "#include <jni.h>\n#include <android/log.h>\n\n\n\nJNIEXPORT void JNICALL\nJava_com_example_nativecpp_MainActivity_clickNative2(JNIEnv *env, jobject thiz) {\n    __android_log_print(ANDROID_LOG_ERROR, \"hello\", \"%s\", \"native2\");\n}"
  },
  {
    "path": "app/src/main/cpp/nativeso3.c",
    "content": "#include <jni.h>\n#include <android/log.h>\n\n//\n// Created by chenhailiang on 2023/4/27.\n//\n\n\nJNIEXPORT void JNICALL\nJava_com_example_nativecpp_MainActivity_clickNative3(JNIEnv *env, jobject thiz) {\n    __android_log_print(ANDROID_LOG_ERROR, \"hello\", \"%s\", \"native3\");\n}"
  },
  {
    "path": "app/src/main/java/com/example/nativecpp/CustomApplication.java",
    "content": "package com.example.nativecpp;\n\nimport android.app.Application;\nimport android.util.Log;\n\n\nimport com.pika.sillyboy.DynamicSoLauncher;\n\nimport java.io.File;\n\nimport kotlin.jvm.functions.Function1;\n\npublic class CustomApplication extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        // data/data/com.example.sillyboy/files/dynamic_so\n        String path = getFilesDir().getAbsolutePath() + \"/dynamic_so/\";\n\n        File file = new File(path);\n        if (!file.exists()) {\n            file.mkdir();\n        }\n\n        // 在合适的时候将自定义路径插入so检索路径 需要使用者自己负责在这个路径上有写入权限\n        DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> {\n            // 处理一些自定义逻辑\n            return true;\n        });\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/nativecpp/MainActivity.java",
    "content": "package com.example.nativecpp;\n\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport android.os.Bundle;\nimport android.util.Log;\n\nimport com.example.nativecpp.databinding.ActivityMainBinding;\nimport com.pika.sillyboy.DynamicLoad;\nimport com.pika.sillyboy.DynamicSoLauncher;\n\n\nimport java.io.File;\n\n// 把这个注解删掉，System.loadLibrary就会走正常的流程，否则就会走插桩流程\n//@DynamicLoad\npublic class MainActivity extends AppCompatActivity {\n\n\n    private ActivityMainBinding binding;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        String path = getFilesDir().getAbsolutePath() + \"/dynamic_so/\";\n\n        Log.i(\"hello\", \"path:\" + path);\n        File file = new File(path + \"libnative3.so\");\n\n        binding = ActivityMainBinding.inflate(getLayoutInflater());\n        setContentView(binding.getRoot());\n        // 正常的流程System.loadLibrary\n        binding.loadNormal.setOnClickListener(v -> System.loadLibrary(\"native3\"));\n\n        // 调用动态so库\n        binding.loadDynamic.setOnClickListener(v -> DynamicSoLauncher.INSTANCE.loadSoDynamically(file));\n        binding.native1.setOnClickListener(v -> clickNative1());\n\n        binding.native2.setOnClickListener(v -> clickNative2());\n\n        binding.native3.setOnClickListener(v -> clickNative3());\n\n    }\n\n\n    public native void clickNative1();\n    public native void clickNative2();\n    public native void clickNative3();\n}\n\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".MainActivity\">\n\n\n    <TextView\n        android:text=\"pika:see log !!!!!\"\n        android:textStyle=\"bold\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"/>\n\n\n\n    <Button\n        android:id=\"@+id/native1\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"click native 1!\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <Button\n        android:id=\"@+id/native2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"click native 2!\"\n        app:layout_constraintTop_toBottomOf=\"@id/native1\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n       />\n\n    <Button\n        android:id=\"@+id/native3\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"click native 3!\"\n        app:layout_constraintTop_toBottomOf=\"@id/native2\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        />\n\n    <Button\n        android:id=\"@+id/load_normal\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"system load \"\n        app:layout_constraintTop_toBottomOf=\"@id/native3\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/load_dynamic\" />\n\n    <Button\n        android:id=\"@+id/load_dynamic\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"dynamic load\"\n        app:layout_constraintTop_toBottomOf=\"@id/native3\"\n        app:layout_constraintLeft_toRightOf=\"@+id/load_normal\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">sillyboy</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.NativeCpp\" parent=\"Theme.MaterialComponents.DayNight.DarkActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_500</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorOnPrimary\">@color/white</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_700</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">?attr/colorPrimaryVariant</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.NativeCpp\" parent=\"Theme.MaterialComponents.DayNight.DarkActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_200</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorOnPrimary\">@color/black</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_200</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">?attr/colorPrimaryVariant</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "app/src/test/java/com/example/nativecpp/ExampleUnitTest.java",
    "content": "package com.example.nativecpp;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n\n\nbuildscript {\n    repositories {\n        maven {url uri('./repositories')}\n        google()\n        mavenCentral()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.6.4'\n        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10'\n        //classpath 'com.pika.plugin:sillyboy:1.0.2'\n    }\n\n}\ntasks.withType(JavaCompile) {\n    sourceCompatibility = JavaVersion.VERSION_1_8\n    targetCompatibility = JavaVersion.VERSION_1_8\n}\n\nallprojects {\n    repositories {\n        google()\n        mavenCentral()\n    }\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sat Jan 08 09:59:34 CST 2022\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.5-all.zip\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n\r\n@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "lib_sillyboy/.gitignore",
    "content": "/build"
  },
  {
    "path": "lib_sillyboy/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n    id 'org.jetbrains.kotlin.android'\n}\n\nandroid {\n    compileSdkVersion 32\n\n    defaultConfig {\n        minSdkVersion 21\n        targetSdkVersion 32\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n\n    kotlinOptions {\n        jvmTarget = \"1.8\"\n    }\n\n}\n\n\n\n\ndependencies {\n\n    implementation 'androidx.appcompat:appcompat:1.4.2'\n    implementation 'com.google.android.material:material:1.6.1'\n}"
  },
  {
    "path": "lib_sillyboy/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "lib_sillyboy/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "lib_sillyboy/src/androidTest/java/com/example/lib_sillyboy/ExampleInstrumentedTest.java",
    "content": "package com.example.lib_sillyboy;\n\nimport android.content.Context;\n\nimport androidx.test.platform.app.InstrumentationRegistry;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static org.junit.Assert.*;\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\n@RunWith(AndroidJUnit4.class)\npublic class ExampleInstrumentedTest {\n    @Test\n    public void useAppContext() {\n        // Context of the app under test.\n        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();\n        assertEquals(\"com.example.lib_sillyboy.test\", appContext.getPackageName());\n    }\n}"
  },
  {
    "path": "lib_sillyboy/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.example.lib_sillyboy\">\n\n</manifest>"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicLoad.kt",
    "content": "package com.pika.sillyboy\n\nimport androidx.annotation.Keep\n\n\n@Keep\n@Target(AnnotationTarget.CLASS)\n@Retention(AnnotationRetention.RUNTIME)\nannotation class DynamicLoad()\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSo.java",
    "content": "package com.pika.sillyboy;\n\nimport android.content.Context;\n\nimport com.pika.sillyboy.elf.ElfParser;\nimport com.pika.sillyboy.pathinsert.LoadLibraryUtils;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.List;\n\n\nclass DynamicSo {\n    public static void loadSoDynamically(File soFIle, String path) {\n        try {\n            ElfParser parser = null;\n            final List<String> dependencies;\n            try {\n                parser = new ElfParser(soFIle);\n                dependencies = parser.parseNeededDependencies();\n            } finally {\n                if (parser != null) {\n                    parser.close();\n                }\n            }\n            //如果nativecpp3->nativecpptwo->nativecpp 则先加载 DynamicSo.loadStaticSo(nativecpptwo)，此时nativecpp作为nativecpptwo的直接依赖被加载了\n            //不能直接加载nativecpp3，导致加载直接依赖nativetwo的时候nativecpp没加载导致错误。 这个可以优化，比如递归\n            for (final String dependency : dependencies) {\n\n                try {\n                    File file = new File(path + dependency);\n                    if (file.exists()) {\n                        //递归查找\n                        loadSoDynamically(file, path);\n                    } else {\n                        // so文件不存在这个文件夹，代表是ndk中的so，如liblog.so，则直接加载\n                        // 把本来lib前缀和.so后缀去掉即可\n                        String dependencySo = dependency.substring(3, dependency.length() - 3);\n                        //在application已经注入了路径DynamicSo.insertPathToNativeSystem(this,file) 所以采用系统的加载就行\n                        System.loadLibrary(dependencySo);\n                    }\n\n\n                } catch (Exception e) {\n                    e.printStackTrace();\n                }\n\n            }\n        } catch (IOException ignored) {\n        }\n        // 先把依赖项加载完，再加载本身\n        System.loadLibrary(soFIle.getName().substring(3, soFIle.getName().length() - 3));\n    }\n\n    public static void insertPathToNativeSystem(Context context,File file){\n        try {\n            LoadLibraryUtils.installNativeLibraryPath(context.getClassLoader(), file);\n        } catch (Throwable e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/DynamicSoLauncher.kt",
    "content": "package com.pika.sillyboy\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.annotation.Keep\nimport java.io.File\n\n@Keep\nobject DynamicSoLauncher {\n    private var soPath = \"\"\n    private var beforeSoLoadListener: ((soName: String) -> Boolean)? = null\n    fun initDynamicSoConfig(context: Context, soPath: String,beforeLoadListener: ((soName: String) -> Boolean)?=null) {\n        this.soPath = soPath\n        this.beforeSoLoadListener = beforeLoadListener\n        DynamicSo.insertPathToNativeSystem(context, File(soPath))\n    }\n\n    fun loadSoDynamically(file: File) {\n        if (soPath.isEmpty()) {\n            throw RuntimeException(\"you must call initDynamicSoConfig first. The soPath is empty\")\n        }\n        if(beforeSoLoadListener?.invoke(file.name) == false){\n            return\n        }\n        DynamicSo.loadSoDynamically(file, soPath)\n    }\n\n    fun loadSoDynamically(fileName: String) {\n        if (soPath.isEmpty()) {\n            throw RuntimeException(\"you must call initDynamicSoConfig first. The soPath is empty\")\n        }\n        if(beforeSoLoadListener?.invoke(fileName) == false){\n            return\n        }\n        DynamicSo.loadSoDynamically(File(soPath + fileName), soPath)\n    }\n\n\n    // 插件调用\n    @JvmStatic\n    fun loadLibrary(soName: String) {\n        Log.e(\"hello\", soName)\n        val wrapSoName = \"lib${soName}.so\"\n        loadSoDynamically(wrapSoName)\n    }\n}"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic32Structure.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Dynamic32Structure extends Elf.DynamicStructure {\r\n    public Dynamic32Structure(final ElfParser parser, final Elf.Header header,\r\n                              long baseOffset, final int index) throws IOException {\r\n        final ByteBuffer buffer = ByteBuffer.allocate(4);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        baseOffset = baseOffset + (index * 8);\r\n        tag = parser.readWord(buffer, baseOffset);\r\n        val = parser.readWord(buffer, baseOffset + 0x4);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Dynamic64Structure.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Dynamic64Structure extends Elf.DynamicStructure {\r\n    public Dynamic64Structure(final ElfParser parser, final Elf.Header header,\r\n                              long baseOffset, final int index) throws IOException {\r\n        final ByteBuffer buffer = ByteBuffer.allocate(8);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        baseOffset = baseOffset + (index * 16);\r\n        tag = parser.readLong(buffer, baseOffset);\r\n        val = parser.readLong(buffer, baseOffset + 0x8);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\nimport java.io.IOException;\r\n\r\npublic interface Elf {\r\n    abstract class Header {\r\n        public static final int ELFCLASS32 = 1; // 32 Bit ELF\r\n        public static final int ELFCLASS64 = 2; // 64 Bit ELF\r\n        public static final int ELFDATA2MSB = 2; // Big Endian, 2s complement\r\n\r\n        public boolean bigEndian;\r\n        public int type;\r\n        public long phoff;\r\n        public long shoff;\r\n        public int phentsize;\r\n        public int phnum;\r\n        public int shentsize;\r\n        public int shnum;\r\n        public int shstrndx;\r\n\r\n        abstract public SectionHeader getSectionHeader(int index) throws IOException;\r\n        abstract public ProgramHeader getProgramHeader(long index) throws IOException;\r\n        abstract public DynamicStructure getDynamicStructure(long baseOffset, int index)\r\n                throws IOException;\r\n    }\r\n\r\n    abstract class ProgramHeader {\r\n        public static final int PT_LOAD = 1; // Loadable segment\r\n        public static final int PT_DYNAMIC = 2; // Dynamic linking information\r\n\r\n        public long type;\r\n        public long offset;\r\n        public long vaddr;\r\n        public long memsz;\r\n    }\r\n\r\n    abstract class SectionHeader {\r\n        public long info;\r\n    }\r\n\r\n    abstract class DynamicStructure {\r\n        public static final int DT_NULL = 0; // Marks end of structure list\r\n        public static final int DT_NEEDED = 1; // Needed library\r\n        public static final int DT_STRTAB = 5; // String table\r\n\r\n        public long tag;\r\n        public long val; // Union with d_ptr\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf32Header.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Elf32Header extends Elf.Header {\r\n    private final ElfParser parser;\r\n\r\n    public Elf32Header(final boolean bigEndian, final ElfParser parser) throws IOException {\r\n        this.bigEndian = bigEndian;\r\n        this.parser = parser;\r\n\r\n        final ByteBuffer buffer = ByteBuffer.allocate(4);\r\n        buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        type = parser.readHalf(buffer, 0x10);\r\n        phoff = parser.readWord(buffer, 0x1C);\r\n        shoff = parser.readWord(buffer, 0x20);\r\n        phentsize = parser.readHalf(buffer, 0x2A);\r\n        phnum = parser.readHalf(buffer, 0x2C);\r\n        shentsize = parser.readHalf(buffer, 0x2E);\r\n        shnum = parser.readHalf(buffer, 0x30);\r\n        shstrndx = parser.readHalf(buffer, 0x32);\r\n    }\r\n\r\n    @Override\r\n    public Elf.SectionHeader getSectionHeader(final int index) throws IOException {\r\n        return new Section32Header(parser, this, index);\r\n    }\r\n\r\n    @Override\r\n    public Elf.ProgramHeader getProgramHeader(final long index) throws IOException {\r\n        return new Program32Header(parser, this, index);\r\n    }\r\n\r\n    @Override\r\n    public Elf.DynamicStructure getDynamicStructure(final long baseOffset, final int index)\r\n            throws IOException {\r\n        return new Dynamic32Structure(parser, this, baseOffset, index);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Elf64Header.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Elf64Header extends Elf.Header {\r\n    private final ElfParser parser;\r\n\r\n    public Elf64Header(final boolean bigEndian, final ElfParser parser) throws IOException {\r\n        this.bigEndian = bigEndian;\r\n        this.parser = parser;\r\n\r\n        final ByteBuffer buffer = ByteBuffer.allocate(8);\r\n        buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        type = parser.readHalf(buffer, 0x10);\r\n        phoff = parser.readLong(buffer, 0x20);\r\n        shoff = parser.readLong(buffer, 0x28);\r\n        phentsize = parser.readHalf(buffer, 0x36);\r\n        phnum = parser.readHalf(buffer, 0x38);\r\n        shentsize = parser.readHalf(buffer, 0x3A);\r\n        shnum = parser.readHalf(buffer, 0x3C);\r\n        shstrndx = parser.readHalf(buffer, 0x3E);\r\n    }\r\n\r\n    @Override\r\n    public Elf.SectionHeader getSectionHeader(final int index) throws IOException {\r\n        return new Section64Header(parser, this, index);\r\n    }\r\n\r\n    @Override\r\n    public Elf.ProgramHeader getProgramHeader(final long index) throws IOException {\r\n        return new Program64Header(parser, this, index);\r\n    }\r\n\r\n    @Override\r\n    public Elf.DynamicStructure getDynamicStructure(final long baseOffset, final int index)\r\n            throws IOException {\r\n        return new Dynamic64Structure(parser, this, baseOffset, index);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/ElfParser.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\n\r\nimport java.io.Closeable;\r\nimport java.io.EOFException;\r\nimport java.io.File;\r\nimport java.io.FileInputStream;\r\nimport java.io.FileNotFoundException;\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\nimport java.nio.channels.FileChannel;\r\nimport java.util.ArrayList;\r\nimport java.util.Collections;\r\nimport java.util.List;\r\n\r\npublic class ElfParser implements Closeable, Elf {\r\n    private final int MAGIC = 0x464C457F;\r\n    private final FileChannel channel;\r\n\r\n    public ElfParser(File file) throws FileNotFoundException {\r\n        if (file == null || !file.exists()) {\r\n            throw new IllegalArgumentException(\"File is null or does not exist\");\r\n        }\r\n\r\n        final FileInputStream inputStream = new FileInputStream(file);\r\n        this.channel = inputStream.getChannel();\r\n    }\r\n\r\n    public Header parseHeader() throws IOException {\r\n        channel.position(0L);\r\n\r\n        // Read in ELF identification to determine file class and endianness\r\n        final ByteBuffer buffer = ByteBuffer.allocate(8);\r\n        buffer.order(ByteOrder.LITTLE_ENDIAN);\r\n        if (readWord(buffer, 0) != MAGIC) {\r\n            throw new IllegalArgumentException(\"Invalid ELF Magic!\");\r\n        }\r\n\r\n        final short fileClass = readByte(buffer, 0x4);\r\n        final boolean bigEndian = (readByte(buffer, 0x5) == Header.ELFDATA2MSB);\r\n        if (fileClass == Header.ELFCLASS32) {\r\n            return new Elf32Header(bigEndian, this);\r\n        } else if (fileClass == Header.ELFCLASS64) {\r\n            return new Elf64Header(bigEndian, this);\r\n        }\r\n\r\n        throw new IllegalStateException(\"Invalid class type!\");\r\n    }\r\n\r\n    public List<String> parseNeededDependencies() throws IOException {\r\n        channel.position(0);\r\n        final List<String> dependencies = new ArrayList<String>();\r\n        final Header header = parseHeader();\r\n        final ByteBuffer buffer = ByteBuffer.allocate(8);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        long numProgramHeaderEntries = header.phnum;\r\n        if (numProgramHeaderEntries == 0xFFFF) {\r\n            /**\r\n             * Extended Numbering\r\n             *\r\n             * If the real number of program header table entries is larger than\r\n             * or equal to PN_XNUM(0xffff), it is set to sh_info field of the\r\n             * section header at index 0, and PN_XNUM is set to e_phnum\r\n             * field. Otherwise, the section header at index 0 is zero\r\n             * initialized, if it exists.\r\n             **/\r\n            final SectionHeader sectionHeader = header.getSectionHeader(0);\r\n            numProgramHeaderEntries = sectionHeader.info;\r\n        }\r\n\r\n        long dynamicSectionOff = 0;\r\n        for (long i = 0; i < numProgramHeaderEntries; ++i) {\r\n            final ProgramHeader programHeader = header.getProgramHeader(i);\r\n            if (programHeader.type == ProgramHeader.PT_DYNAMIC) {\r\n                dynamicSectionOff = programHeader.offset;\r\n                break;\r\n            }\r\n        }\r\n\r\n        if (dynamicSectionOff == 0) {\r\n            // No dynamic linking info, nothing to load\r\n            return Collections.unmodifiableList(dependencies);\r\n        }\r\n\r\n        int i = 0;\r\n        final List<Long> neededOffsets = new ArrayList<Long>();\r\n        long vStringTableOff = 0;\r\n        DynamicStructure dynStructure;\r\n        do {\r\n            dynStructure = header.getDynamicStructure(dynamicSectionOff, i);\r\n            if (dynStructure.tag == DynamicStructure.DT_NEEDED) {\r\n                neededOffsets.add(dynStructure.val);\r\n            } else if (dynStructure.tag == DynamicStructure.DT_STRTAB) {\r\n                vStringTableOff = dynStructure.val; // d_ptr union\r\n            }\r\n            ++i;\r\n        } while (dynStructure.tag != DynamicStructure.DT_NULL);\r\n\r\n        if (vStringTableOff == 0) {\r\n            throw new IllegalStateException(\"String table offset not found!\");\r\n        }\r\n\r\n        // Map to file offset\r\n        final long stringTableOff = offsetFromVma(header, numProgramHeaderEntries, vStringTableOff);\r\n        for (final Long strOff : neededOffsets) {\r\n            dependencies.add(readString(buffer, stringTableOff + strOff));\r\n        }\r\n\r\n        return dependencies;\r\n    }\r\n\r\n    private long offsetFromVma(final Header header, final long numEntries, final long vma)\r\n            throws IOException {\r\n        for (long i = 0; i < numEntries; ++i) {\r\n            final ProgramHeader programHeader = header.getProgramHeader(i);\r\n            if (programHeader.type == ProgramHeader.PT_LOAD) {\r\n                // Within memsz instead of filesz to be more tolerant\r\n                if (programHeader.vaddr <= vma\r\n                        && vma <= programHeader.vaddr + programHeader.memsz) {\r\n                    return vma - programHeader.vaddr + programHeader.offset;\r\n                }\r\n            }\r\n        }\r\n\r\n        throw new IllegalStateException(\"Could not map vma to file offset!\");\r\n    }\r\n\r\n    @Override\r\n    public void close() throws IOException {\r\n        this.channel.close();\r\n    }\r\n\r\n    protected String readString(final ByteBuffer buffer, long offset) throws IOException {\r\n        final StringBuilder builder = new StringBuilder();\r\n        short c;\r\n        while ((c = readByte(buffer, offset++)) != 0) {\r\n            builder.append((char) c);\r\n        }\r\n\r\n        return builder.toString();\r\n    }\r\n\r\n    protected long readLong(final ByteBuffer buffer, final long offset) throws IOException {\r\n        read(buffer, offset, 8);\r\n        return buffer.getLong();\r\n    }\r\n\r\n    protected long readWord(final ByteBuffer buffer, final long offset) throws IOException {\r\n        read(buffer, offset, 4);\r\n        return buffer.getInt() & 0xFFFFFFFFL;\r\n    }\r\n\r\n    protected int readHalf(final ByteBuffer buffer, final long offset) throws IOException {\r\n        read(buffer, offset, 2);\r\n        return buffer.getShort() & 0xFFFF;\r\n    }\r\n\r\n    protected short readByte(final ByteBuffer buffer, final long offset) throws IOException {\r\n        read(buffer, offset, 1);\r\n        return (short) (buffer.get() & 0xFF);\r\n    }\r\n\r\n    protected void read(final ByteBuffer buffer, long offset, final int length) throws IOException {\r\n        buffer.position(0);\r\n        buffer.limit(length);\r\n        long bytesRead = 0;\r\n        while (bytesRead < length) {\r\n            final int read = channel.read(buffer, offset + bytesRead);\r\n            if (read == -1) {\r\n                throw new EOFException();\r\n            }\r\n\r\n            bytesRead += read;\r\n        }\r\n        buffer.position(0);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program32Header.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Program32Header extends Elf.ProgramHeader {\r\n    public Program32Header(final ElfParser parser, final Elf.Header header, final long index)\r\n            throws IOException {\r\n        final ByteBuffer buffer = ByteBuffer.allocate(4);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        final long baseOffset = header.phoff + (index * header.phentsize);\r\n        type = parser.readWord(buffer, baseOffset);\r\n        offset = parser.readWord(buffer, baseOffset + 0x4);\r\n        vaddr = parser.readWord(buffer, baseOffset + 0x8);\r\n        memsz = parser.readWord(buffer, baseOffset + 0x14);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Program64Header.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Program64Header extends Elf.ProgramHeader {\r\n    public Program64Header(final ElfParser parser, final Elf.Header header, final long index)\r\n            throws IOException {\r\n        final ByteBuffer buffer = ByteBuffer.allocate(8);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        final long baseOffset = header.phoff + (index * header.phentsize);\r\n        type = parser.readWord(buffer, baseOffset);\r\n        offset = parser.readLong(buffer, baseOffset + 0x8);\r\n        vaddr = parser.readLong(buffer, baseOffset + 0x10);\r\n        memsz = parser.readLong(buffer, baseOffset + 0x28);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section32Header.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Section32Header extends Elf.SectionHeader {\r\n    public Section32Header(final ElfParser parser, final Elf.Header header, final int index)\r\n            throws IOException {\r\n        final ByteBuffer buffer = ByteBuffer.allocate(4);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        info = parser.readWord(buffer, header.shoff + (index * header.shentsize) + 0x1C);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/elf/Section64Header.java",
    "content": "/**\r\n * Copyright 2015 - 2016 KeepSafe Software, Inc.\r\n *\r\n * Licensed under the Apache License, Version 2.0 (the \"License\");\r\n * you may not use this file except in compliance with the License.\r\n * You may obtain a copy of the License at\r\n *\r\n * http://www.apache.org/licenses/LICENSE-2.0\r\n *\r\n * Unless required by applicable law or agreed to in writing, software\r\n * distributed under the License is distributed on an \"AS IS\" BASIS,\r\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n * See the License for the specific language governing permissions and\r\n * limitations under the License.\r\n */\r\npackage com.pika.sillyboy.elf;\r\n\r\n\r\nimport java.io.IOException;\r\nimport java.nio.ByteBuffer;\r\nimport java.nio.ByteOrder;\r\n\r\npublic class Section64Header extends Elf.SectionHeader {\r\n    public Section64Header(final ElfParser parser, final Elf.Header header, final int index)\r\n            throws IOException {\r\n        final ByteBuffer buffer = ByteBuffer.allocate(8);\r\n        buffer.order(header.bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);\r\n\r\n        info = parser.readWord(buffer, header.shoff + (index * header.shentsize) + 0x2C);\r\n    }\r\n}\r\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/LoadLibraryUtils.java",
    "content": "/*\n * Tencent is pleased to support the open source community by making Tinker available.\n *\n * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * Licensed under the BSD 3-Clause License (the \"License\"); you may not use this file except in\n * compliance with the License. You may obtain a copy of the License at\n *\n * https://opensource.org/licenses/BSD-3-Clause\n *\n * Unless required by applicable law or agreed to in writing, software distributed under the License is\n * distributed on an \"AS IS\" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,\n * either express or implied. See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.pika.sillyboy.pathinsert;\n\nimport android.os.Build;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\n\n\npublic class LoadLibraryUtils {\n    private static final String TAG = \"LoadLibrary\";\n\n    public static void installNativeLibraryPath(ClassLoader classLoader, File folder)\n            throws Throwable {\n        if (folder == null || !folder.exists()) {\n            Log.e(TAG, \"installNativeLibraryPath, folder is illegal\");\n            return;\n        }\n        // android o sdk_int 26\n        // for android o preview sdk_int 25\n        if ((Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0)\n                || Build.VERSION.SDK_INT > 25) {\n            try {\n                V25.install(classLoader, folder);\n            } catch (Throwable throwable) {\n                // install fail, try to treat it as v23\n                // some preview N version may go here\n                Log.e(TAG, String.format(\"installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23\",\n                        Build.VERSION.SDK_INT, throwable.getMessage()));\n                V23.install(classLoader, folder);\n            }\n        } else if (Build.VERSION.SDK_INT >= 23) {\n            try {\n                V23.install(classLoader, folder);\n            } catch (Throwable throwable) {\n                // install fail, try to treat it as v14\n                Log.e(TAG, String.format(\"installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14\",\n                        Build.VERSION.SDK_INT, throwable.getMessage()));\n\n                V14.install(classLoader, folder);\n            }\n        } else if (Build.VERSION.SDK_INT >= 14) {\n            V14.install(classLoader, folder);\n        } else {\n            V4.install(classLoader, folder);\n        }\n    }\n\n    private static final class V4 {\n        private static void install(ClassLoader classLoader, File folder)  throws Throwable {\n            String addPath = folder.getPath();\n            Field pathField = ShareReflectUtil.findField(classLoader, \"libPath\");\n            final String origLibPaths = (String) pathField.get(classLoader);\n            final String[] origLibPathSplit = origLibPaths.split(\":\");\n            final StringBuilder newLibPaths = new StringBuilder(addPath);\n\n            for (String origLibPath : origLibPathSplit) {\n                if (origLibPath == null || addPath.equals(origLibPath)) {\n                    continue;\n                }\n                newLibPaths.append(':').append(origLibPath);\n            }\n            pathField.set(classLoader, newLibPaths.toString());\n\n            final Field libraryPathElementsFiled = ShareReflectUtil.findField(classLoader, \"libraryPathElements\");\n            final List<String> libraryPathElements = (List<String>) libraryPathElementsFiled.get(classLoader);\n            final Iterator<String> libPathElementIt = libraryPathElements.iterator();\n            while (libPathElementIt.hasNext()) {\n                final String libPath = libPathElementIt.next();\n                if (addPath.equals(libPath)) {\n                    libPathElementIt.remove();\n                    break;\n                }\n            }\n            libraryPathElements.add(0, addPath);\n            libraryPathElementsFiled.set(classLoader, libraryPathElements);\n        }\n    }\n\n    private static final class V14 {\n        private static void install(ClassLoader classLoader, File folder)  throws Throwable {\n            final Field pathListField = ShareReflectUtil.findField(classLoader, \"pathList\");\n            final Object dexPathList = pathListField.get(classLoader);\n\n            final Field nativeLibDirField = ShareReflectUtil.findField(dexPathList, \"nativeLibraryDirectories\");\n            final File[] origNativeLibDirs = (File[]) nativeLibDirField.get(dexPathList);\n\n            final List<File> newNativeLibDirList = new ArrayList<>(origNativeLibDirs.length + 1);\n            newNativeLibDirList.add(folder);\n            for (File origNativeLibDir : origNativeLibDirs) {\n                if (!folder.equals(origNativeLibDir)) {\n                    newNativeLibDirList.add(origNativeLibDir);\n                }\n            }\n            nativeLibDirField.set(dexPathList, newNativeLibDirList.toArray(new File[0]));\n        }\n    }\n\n    private static final class V23 {\n        private static void install(ClassLoader classLoader, File folder)  throws Throwable {\n            final Field pathListField = ShareReflectUtil.findField(classLoader, \"pathList\");\n            final Object dexPathList = pathListField.get(classLoader);\n\n            final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, \"nativeLibraryDirectories\");\n\n            List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);\n            if (origLibDirs == null) {\n                origLibDirs = new ArrayList<>(2);\n            }\n            final Iterator<File> libDirIt = origLibDirs.iterator();\n            while (libDirIt.hasNext()) {\n                final File libDir = libDirIt.next();\n                if (folder.equals(libDir)) {\n                    libDirIt.remove();\n                    break;\n                }\n            }\n            origLibDirs.add(0, folder);\n\n            final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, \"systemNativeLibraryDirectories\");\n            List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);\n            if (origSystemLibDirs == null) {\n                origSystemLibDirs = new ArrayList<>(2);\n            }\n\n            final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);\n            newLibDirs.addAll(origLibDirs);\n            newLibDirs.addAll(origSystemLibDirs);\n\n            final Method makeElements = ShareReflectUtil.findMethod(dexPathList,\n                    \"makePathElements\", List.class, File.class, List.class);\n            final ArrayList<IOException> suppressedExceptions = new ArrayList<>();\n\n            final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs, null, suppressedExceptions);\n\n            final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, \"nativeLibraryPathElements\");\n            nativeLibraryPathElements.set(dexPathList, elements);\n        }\n    }\n\n    private static final class V25 {\n        private static void install(ClassLoader classLoader, File folder)  throws Throwable {\n            final Field pathListField = ShareReflectUtil.findField(classLoader, \"pathList\");\n            final Object dexPathList = pathListField.get(classLoader);\n\n            final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, \"nativeLibraryDirectories\");\n\n            List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);\n            if (origLibDirs == null) {\n                origLibDirs = new ArrayList<>(2);\n            }\n            final Iterator<File> libDirIt = origLibDirs.iterator();\n            while (libDirIt.hasNext()) {\n                final File libDir = libDirIt.next();\n                if (folder.equals(libDir)) {\n                    libDirIt.remove();\n                    break;\n                }\n            }\n            origLibDirs.add(0, folder);\n\n            final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, \"systemNativeLibraryDirectories\");\n            List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);\n            if (origSystemLibDirs == null) {\n                origSystemLibDirs = new ArrayList<>(2);\n            }\n\n            final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);\n            newLibDirs.addAll(origLibDirs);\n            newLibDirs.addAll(origSystemLibDirs);\n\n            final Method makeElements = ShareReflectUtil.findMethod(dexPathList, \"makePathElements\", List.class);\n\n            final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);\n\n            final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, \"nativeLibraryPathElements\");\n            nativeLibraryPathElements.set(dexPathList, elements);\n        }\n    }\n}\n"
  },
  {
    "path": "lib_sillyboy/src/main/java/com/pika/sillyboy/pathinsert/ShareReflectUtil.java",
    "content": "package com.pika.sillyboy.pathinsert;\n\nimport android.content.Context;\n\nimport java.lang.reflect.Array;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\n\npublic class ShareReflectUtil {\n\n    /**\n     * Locates a given field anywhere in the class inheritance hierarchy.\n     *\n     * @param instance an object to search the field into.\n     * @param name     field name\n     * @return a field object\n     * @throws NoSuchFieldException if the field cannot be located\n     */\n    public static Field findField(Object instance, String name) throws NoSuchFieldException {\n        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {\n            try {\n                Field field = clazz.getDeclaredField(name);\n\n                if (!field.isAccessible()) {\n                    field.setAccessible(true);\n                }\n\n                return field;\n            } catch (NoSuchFieldException e) {\n                // ignore and search next\n            }\n        }\n\n        throw new NoSuchFieldException(\"Field \" + name + \" not found in \" + instance.getClass());\n    }\n\n    public static Field findField(Class<?> originClazz, String name) throws NoSuchFieldException {\n        for (Class<?> clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) {\n            try {\n                Field field = clazz.getDeclaredField(name);\n\n                if (!field.isAccessible()) {\n                    field.setAccessible(true);\n                }\n\n                return field;\n            } catch (NoSuchFieldException e) {\n                // ignore and search next\n            }\n        }\n\n        throw new NoSuchFieldException(\"Field \" + name + \" not found in \" + originClazz);\n    }\n\n    /**\n     * Locates a given method anywhere in the class inheritance hierarchy.\n     *\n     * @param instance       an object to search the method into.\n     * @param name           method name\n     * @param parameterTypes method parameter types\n     * @return a method object\n     * @throws NoSuchMethodException if the method cannot be located\n     */\n    public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)\n        throws NoSuchMethodException {\n        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {\n            try {\n                Method method = clazz.getDeclaredMethod(name, parameterTypes);\n\n                if (!method.isAccessible()) {\n                    method.setAccessible(true);\n                }\n\n                return method;\n            } catch (NoSuchMethodException e) {\n                // ignore and search next\n            }\n        }\n\n        throw new NoSuchMethodException(\"Method \"\n            + name\n            + \" with parameters \"\n            + Arrays.asList(parameterTypes)\n            + \" not found in \" + instance.getClass());\n    }\n\n    /**\n     * Locates a given method anywhere in the class inheritance hierarchy.\n     *\n     * @param clazz          a class to search the method into.\n     * @param name           method name\n     * @param parameterTypes method parameter types\n     * @return a method object\n     * @throws NoSuchMethodException if the method cannot be located\n     */\n    public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes)\n            throws NoSuchMethodException {\n        for (; clazz != null; clazz = clazz.getSuperclass()) {\n            try {\n                Method method = clazz.getDeclaredMethod(name, parameterTypes);\n\n                if (!method.isAccessible()) {\n                    method.setAccessible(true);\n                }\n\n                return method;\n            } catch (NoSuchMethodException e) {\n                // ignore and search next\n            }\n        }\n\n        throw new NoSuchMethodException(\"Method \"\n                + name\n                + \" with parameters \"\n                + Arrays.asList(parameterTypes)\n                + \" not found in \" + clazz);\n    }\n\n    /**\n     * Locates a given constructor anywhere in the class inheritance hierarchy.\n     *\n     * @param instance       an object to search the constructor into.\n     * @param parameterTypes constructor parameter types\n     * @return a constructor object\n     * @throws NoSuchMethodException if the constructor cannot be located\n     */\n    public static Constructor<?> findConstructor(Object instance, Class<?>... parameterTypes)\n            throws NoSuchMethodException {\n        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {\n            try {\n                Constructor<?> ctor = clazz.getDeclaredConstructor(parameterTypes);\n\n                if (!ctor.isAccessible()) {\n                    ctor.setAccessible(true);\n                }\n\n                return ctor;\n            } catch (NoSuchMethodException e) {\n                // ignore and search next\n            }\n        }\n\n        throw new NoSuchMethodException(\"Constructor\"\n                + \" with parameters \"\n                + Arrays.asList(parameterTypes)\n                + \" not found in \" + instance.getClass());\n    }\n\n    /**\n     * Replace the value of a field containing a non null array, by a new array containing the\n     * elements of the original array plus the elements of extraElements.\n     *\n     * @param instance      the instance whose field is to be modified.\n     * @param fieldName     the field to modify.\n     * @param extraElements elements to append at the end of the array.\n     */\n    public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)\n        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {\n        Field jlrField = findField(instance, fieldName);\n\n        Object[] original = (Object[]) jlrField.get(instance);\n        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);\n\n        // NOTE: changed to copy extraElements first, for patch load first\n\n        System.arraycopy(extraElements, 0, combined, 0, extraElements.length);\n        System.arraycopy(original, 0, combined, extraElements.length, original.length);\n\n        jlrField.set(instance, combined);\n    }\n\n    /**\n     * Replace the value of a field containing a non null array, by a new array containing the\n     * elements of the original array plus the elements of extraElements.\n     *\n     * @param instance      the instance whose field is to be modified.\n     * @param fieldName     the field to modify.\n     */\n    public static void reduceFieldArray(Object instance, String fieldName, int reduceSize)\n        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {\n        if (reduceSize <= 0) {\n            return;\n        }\n\n        Field jlrField = findField(instance, fieldName);\n\n        Object[] original = (Object[]) jlrField.get(instance);\n        int finalLength = original.length - reduceSize;\n\n        if (finalLength <= 0) {\n            return;\n        }\n\n        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength);\n\n        System.arraycopy(original, reduceSize, combined, 0, finalLength);\n\n        jlrField.set(instance, combined);\n    }\n\n    public static Object getActivityThread(Context context,\n                                           Class<?> activityThread) {\n        try {\n            if (activityThread == null) {\n                activityThread = Class.forName(\"android.app.ActivityThread\");\n            }\n            Method m = activityThread.getMethod(\"currentActivityThread\");\n            m.setAccessible(true);\n            Object currentActivityThread = m.invoke(null);\n            if (currentActivityThread == null && context != null) {\n                // In older versions of Android (prior to frameworks/base 66a017b63461a22842)\n                // the currentActivityThread was built on thread locals, so we'll need to try\n                // even harder\n                Field mLoadedApk = context.getClass().getField(\"mLoadedApk\");\n                mLoadedApk.setAccessible(true);\n                Object apk = mLoadedApk.get(context);\n                Field mActivityThreadField = apk.getClass().getDeclaredField(\"mActivityThread\");\n                mActivityThreadField.setAccessible(true);\n                currentActivityThread = mActivityThreadField.get(apk);\n            }\n            return currentActivityThread;\n        } catch (Throwable ignore) {\n            return null;\n        }\n    }\n\n    /**\n     * Handy method for fetching hidden integer constant value in system classes.\n     *\n     * @param clazz\n     * @param fieldName\n     * @return\n     */\n    public static int getValueOfStaticIntField(Class<?> clazz, String fieldName, int defVal) {\n        try {\n            final Field field = findField(clazz, fieldName);\n            return field.getInt(null);\n        } catch (Throwable thr) {\n            return defVal;\n        }\n    }\n}\n"
  },
  {
    "path": "lib_sillyboy/src/test/java/com/example/lib_sillyboy/ExampleUnitTest.java",
    "content": "package com.example.lib_sillyboy;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * @see <a href=\"http://d.android.com/tools/testing\">Testing documentation</a>\n */\npublic class ExampleUnitTest {\n    @Test\n    public void addition_isCorrect() {\n        assertEquals(4, 2 + 2);\n    }\n}"
  },
  {
    "path": "lib_sillyplugin/.gitignore",
    "content": "/build"
  },
  {
    "path": "lib_sillyplugin/build.gradle",
    "content": "apply plugin: 'groovy'\napply plugin: 'java'\n// gradle 7 以上的删除了maven plugin，改为了maven-publish 可以先改为gradle 6\napply plugin: 'maven'\n\n\ndependencies {\n    implementation gradleApi()\n    implementation localGroovy()\n\n    implementation 'org.ow2.asm:asm:7.2'\n    implementation 'org.ow2.asm:asm-commons:7.2'\n    implementation 'com.android.tools.build:gradle:3.4.1'\n    implementation 'com.google.guava:guava:27.0.1-android'\n\n\n}\n\n\nrepositories {\n    mavenCentral()\n}\n\n\n\nuploadArchives {\n    repositories {\n        mavenDeployer {\n            //本地Maven\n            repository(url: uri('../repositories'))\n            pom.groupId = 'com.pika.plugin'\n            pom.artifactId = 'sillyboy'\n            pom.version = '1.0.2'\n        }\n    }\n}\n\n\n\n\n\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_1_8\n    targetCompatibility = JavaVersion.VERSION_1_8\n}"
  },
  {
    "path": "lib_sillyplugin/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "lib_sillyplugin/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "lib_sillyplugin/src/androidTest/java/com/example/lib_sillyplugin/ExampleInstrumentedTest.kt",
    "content": "package com.example.lib_sillyplugin\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.example.lib_sillyplugin.test\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "lib_sillyplugin/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.lib_sillyplugin\">\n\n</manifest>"
  },
  {
    "path": "lib_sillyplugin/src/main/groovy/com.plugin.core/DynamicPlugin.groovy",
    "content": "package com.plugin.core\n\n\nimport com.android.build.gradle.AppExtension\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\n\nclass DynamicPlugin implements Plugin<Project> {\n    Project project\n    @Override\n    void apply(Project target) {\n        project = target\n        def android = project.extensions.getByType(AppExtension)\n        android.registerTransform(new DynamicTransform(project), Collections.EMPTY_LIST)\n    }\n}\n\n\n"
  },
  {
    "path": "lib_sillyplugin/src/main/groovy/com.plugin.core/DynamicTransform.groovy",
    "content": "package com.plugin.core\n\n\nimport com.android.annotations.NonNull\nimport com.android.build.api.transform.*\nimport com.android.build.gradle.internal.pipeline.TransformManager\nimport com.android.ide.common.internal.WaitableExecutor\nimport com.plugin.helper.SystemLoadHelper\nimport org.apache.commons.io.FileUtils\nimport org.apache.commons.io.IOUtils\nimport org.gradle.api.Project\nimport org.objectweb.asm.ClassReader\nimport org.objectweb.asm.ClassWriter\nimport org.objectweb.asm.tree.AnnotationNode\nimport org.objectweb.asm.tree.ClassNode\n\nimport java.nio.file.Files\nimport java.nio.file.attribute.FileTime\nimport java.util.concurrent.Callable\nimport java.util.function.Consumer\nimport java.util.zip.CRC32\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipOutputStream\n\nclass DynamicTransform extends Transform {\n    private static final FileTime ZERO = FileTime.fromMillis(0)\n    private static final String FILE_SEP = File.separator\n    private WaitableExecutor waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool()\n    private Project project\n\n    DynamicTransform() {\n    }\n\n    DynamicTransform(Project outProject) {\n        this.project = outProject\n    }\n\n    @Override\n    String getName() {\n        return \"DynamicTransform\"\n    }\n\n    @Override\n    Set<QualifiedContent.ContentType> getInputTypes() {\n        return TransformManager.CONTENT_CLASS\n    }\n\n    @Override\n    Set<? super QualifiedContent.Scope> getScopes() {\n        return TransformManager.SCOPE_FULL_PROJECT\n    }\n\n    @Override\n    boolean isIncremental() {\n        return false\n    }\n\n    @Override\n    void transform(@NonNull TransformInvocation transformInvocation) {\n        println '--------------- DynamicTransform start --------------- ' + transformInvocation.incremental\n        def startTime = System.currentTimeMillis()\n        Collection<TransformInput> inputs = transformInvocation.inputs\n        TransformOutputProvider outputProvider = transformInvocation.outputProvider\n\n        if (!transformInvocation.incremental) {\n            if (outputProvider != null) {\n                outputProvider.deleteAll()\n            }\n        }\n\n        boolean flagForCleanDexBuilderFolder = false\n\n        inputs.each { TransformInput input ->\n            input.directoryInputs.each { DirectoryInput directoryInput ->\n                handleDirectoryInput(directoryInput, outputProvider, transformInvocation.incremental)\n            }\n            input.jarInputs.each { JarInput jarInput ->\n                Status status = jarInput.getStatus()\n                File dest = outputProvider.getContentLocation(\n                        jarInput.getFile().getAbsolutePath(),\n                        jarInput.getContentTypes(),\n                        jarInput.getScopes(),\n                        Format.JAR)\n                if (transformInvocation.incremental) {\n                    switch (status) {\n                        case Status.NOTCHANGED:\n                            break\n                        case Status.ADDED:\n                        case Status.CHANGED:\n                            waitableExecutor.execute(new Callable<Object>() {\n                                @Override\n                                Object call() throws Exception {\n                                    transformJar(jarInput.file, dest, jarInput)\n                                    return null\n                                }\n                            })\n                            break\n                        case Status.REMOVED:\n                            if (dest.exists()) {\n                                FileUtils.forceDelete(dest)\n                            }\n                            break\n                    }\n                } else {\n                    if (!transformInvocation.incremental && !flagForCleanDexBuilderFolder) {\n                        cleanDexBuilderFolder(dest)\n                        flagForCleanDexBuilderFolder = true\n                    }\n\n                    transformJar(jarInput.file, dest, jarInput)\n                }\n            }\n        }\n        def costTime = (System.currentTimeMillis() - startTime) / 1000\n        waitableExecutor.waitForTasksWithQuickFail(true)\n        println \"DynamicTransform cost：$costTime s\"\n    }\n\n    byte[] handleTransform(byte[] bytes) {\n        def classNode = new ClassNode()\n        new ClassReader(bytes).accept(classNode, 0)\n        classNode = handleTransform(classNode)\n        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)\n        classNode.accept(cw)\n        return cw.toByteArray()\n    }\n\n    ClassNode handleTransform(ClassNode klass) {\n        def targetClass = false\n        if (klass.visibleAnnotations != null){\n            klass.visibleAnnotations.forEach(new Consumer<AnnotationNode>() {\n                @Override\n                void accept(AnnotationNode annotationNode) {\n                    println annotationNode.desc\n                    if (annotationNode.desc == \"Lcom/pika/sillyboy/DynamicLoad;\"){\n                        targetClass = true\n                    }\n                }\n            })\n        }\n        if (targetClass){\n            SystemLoadHelper.transClass(klass)\n        }\n        return klass\n    }\n\n    void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider, boolean incremental) {\n\n        File dest = outputProvider.getContentLocation(directoryInput.getName(),\n                directoryInput.getContentTypes(), directoryInput.getScopes(),\n                Format.DIRECTORY)\n        FileUtils.forceMkdir(dest)\n        if (incremental) {\n            String srcDirPath = directoryInput.getFile().getAbsolutePath()\n            String destDirPath = dest.getAbsolutePath()\n            Map<File, Status> fileStatusMap = directoryInput.getChangedFiles()\n            for (Map.Entry<File, Status> changedFile : fileStatusMap.entrySet()) {\n                Status status = changedFile.getValue()\n                File inputFile = changedFile.getKey()\n                String destFilePath = inputFile.getAbsolutePath().replace(srcDirPath, destDirPath)\n                File destFile = new File(destFilePath)\n                switch (status) {\n                    case Status.NOTCHANGED:\n                        break\n                    case Status.REMOVED:\n                        if (destFile.exists()) {\n                            destFile.delete()\n                        }\n                        break\n                    case Status.ADDED:\n                    case Status.CHANGED:\n                        try {\n                            FileUtils.touch(destFile)\n                        } catch (IOException e) {\n                            e.printStackTrace()\n                            com.google.common.io.Files.createParentDirs(destFile)\n                        }\n                        transformSingleFile(inputFile, destFile, srcDirPath)\n                        break\n                }\n            }\n        } else {\n            if (directoryInput.file.isDirectory()) {\n                directoryInput.file.eachFileRecurse { File file ->\n                    def name = file.name\n                    if (checkClassFile(name)) {\n                        try {\n                            byte[] code = handleTransform(file.bytes)\n                            FileOutputStream fos = new FileOutputStream(\n                                    file.parentFile.absolutePath + File.separator + name)\n                            fos.write(code)\n                            fos.close()\n                        } catch (Exception e) {\n                            println e.message\n                        }\n\n                    }\n                }\n                FileUtils.copyDirectory(directoryInput.file, dest)\n            }\n        }\n    }\n\n    private void transformSingleFile(final File inputFile, final File outputFile, final String srcBaseDir) {\n        weaveSingleClassToFile(inputFile, outputFile, srcBaseDir)\n    }\n\n    final void weaveSingleClassToFile(File inputFile, File outputFile, String inputBaseDir) throws IOException {\n        if (!inputBaseDir.endsWith(FILE_SEP)) {\n            inputBaseDir = inputBaseDir + FILE_SEP\n        }\n        if (isInjectClass(inputFile.getAbsolutePath().replace(inputBaseDir, \"\").replace(FILE_SEP, \".\"))) {\n            FileUtils.touch(outputFile)\n            byte[] bytes = handleTransform(inputFile.bytes)\n            FileOutputStream fos = new FileOutputStream(outputFile)\n            fos.write(bytes)\n            fos.close()\n            inputStream.close()\n        } else {\n            if (inputFile.isFile()) {\n                FileUtils.touch(outputFile)\n                FileUtils.copyFile(inputFile, outputFile)\n            }\n        }\n    }\n\n    static boolean isInjectClass(String fullQualifiedClassName) {\n        return fullQualifiedClassName.endsWith(\".class\") && !fullQualifiedClassName.contains(\"R\\$\") && !fullQualifiedClassName.contains(\"R.class\") && !fullQualifiedClassName.contains(\"BuildConfig.class\")\n    }\n\n    void transformJar(File inputJar, File outputJar, JarInput jarInput) throws IOException {\n        ZipFile inputZip = new ZipFile(inputJar)\n        ZipOutputStream outputZip = new ZipOutputStream(new BufferedOutputStream(\n                Files.newOutputStream(outputJar.toPath())))\n        Enumeration<? extends ZipEntry> inEntries = inputZip.entries()\n        while (inEntries.hasMoreElements()) {\n            ZipEntry entry = inEntries.nextElement()\n            InputStream originalFile =\n                    new BufferedInputStream(inputZip.getInputStream(entry))\n            ZipEntry outEntry = new ZipEntry(entry.getName())\n            byte[] newEntryContent\n            if (!checkClassFile(outEntry.getName().replace(\"/\", \".\"))) {\n                newEntryContent = IOUtils.toByteArray(originalFile)\n            } else {\n                newEntryContent = handleTransform(IOUtils.toByteArray(originalFile))\n            }\n            CRC32 crc32 = new CRC32()\n            crc32.update(newEntryContent)\n            outEntry.setCrc(crc32.getValue())\n            outEntry.setMethod(ZipEntry.STORED)\n            outEntry.setSize(newEntryContent.length)\n            outEntry.setCompressedSize(newEntryContent.length)\n            outEntry.setLastAccessTime(ZERO)\n            outEntry.setLastModifiedTime(ZERO)\n            outEntry.setCreationTime(ZERO)\n            outputZip.putNextEntry(outEntry)\n            outputZip.write(newEntryContent)\n            outputZip.closeEntry()\n        }\n        outputZip.flush()\n        outputZip.close()\n    }\n\n    private void cleanDexBuilderFolder(File dest) {\n        try {\n            String dexBuilderDir = replaceLastPart(dest.getAbsolutePath(), getName(), \"dexBuilder\")\n            File file = new File(dexBuilderDir).getParentFile()\n            if (file.exists() && file.isDirectory()) {\n                com.android.utils.FileUtils.deleteDirectoryContents(file)\n            }\n        } catch (Exception e) {\n            e.printStackTrace()\n        }\n    }\n\n    private static String replaceLastPart(String originString, String replacement, String toreplace) {\n        int start = originString.lastIndexOf(replacement)\n        StringBuilder builder = new StringBuilder()\n        builder.append(originString.substring(0, start))\n        builder.append(toreplace)\n        builder.append(originString.substring(start + replacement.length()))\n        return builder.toString()\n    }\n\n    static boolean checkClassFile(String name) {\n        return (name.endsWith(\".class\") && !name.startsWith(\"R\\$\")\n                && name != \"R.class\" && !name.startsWith(\"BR\\$\")\n                && name != \"BR.class\" && name != \"BuildConfig.class\" && !name.startsWith(\"kotlinx\"))\n    }\n}\n"
  },
  {
    "path": "lib_sillyplugin/src/main/java/com/plugin/helper/SystemLoadHelper.java",
    "content": "package com.plugin.helper;\n\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.MethodInsnNode;\n\n// power by pika\n\n/*\npublic static void loadLibrary(String libname) {\n        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);\n        }\n */\npublic class SystemLoadHelper {\n    private static String PACKAGE_PATH = \"com/pika/sillyboy\";\n    private static String OWNER = \"java/lang/System\";\n    private static String METHOD_NAME =  \"loadLibrary\";\n    private static String METHOD_DESC = \"(Ljava/lang/String;)V\";\n    private static String DYNAMIC_OWNER  = \"com/pika/sillyboy/DynamicSoLauncher\";\n    public static void transClass(ClassNode classNode) {\n        if (classNode.name.startsWith(PACKAGE_PATH)){\n            return;\n        }\n        classNode.methods.forEach(methodNode -> methodNode.instructions.forEach(abstractInsnNode -> {\n            // 如果是InvokeStatic才继续进行\n            if (abstractInsnNode.getOpcode() == Opcodes.INVOKESTATIC) {\n                transformInvokeStatic((MethodInsnNode) abstractInsnNode);\n            }\n        }));\n    }\n\n    static void transformInvokeStatic(MethodInsnNode methodInsnNode) {\n        // (Ljava/lang/String;)V loadLibrary java/lang/System\n        if (OWNER.equals(methodInsnNode.owner) && METHOD_NAME.equals(methodInsnNode.name) && METHOD_DESC.equals(methodInsnNode.desc)) {\n            methodInsnNode.owner = DYNAMIC_OWNER;\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "lib_sillyplugin/src/main/resources/META-INF/gradle-plugins/com.plugins.core.properties",
    "content": "implementation-class=com.plugin.core.DynamicPlugin"
  },
  {
    "path": "lib_sillyplugin/src/test/java/com/example/lib_sillyplugin/ExampleUnitTest.kt",
    "content": "package com.example.lib_sillyplugin\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "settings.gradle",
    "content": "\nrootProject.name = \"sillyboy\"\ninclude ':app'\ninclude ':lib_sillyboy'\ninclude ':lib_sillyplugin'\n"
  }
]