[
  {
    "path": ".gitattributes",
    "content": "*.prop text eol=lf\n*.sh text eol=lf"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n.idea\n/.idea/caches/build_file_checksums.ser\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n.DS_Store\n/build\n/captures\n/out\n.externalNativeBuild\n.cxx\ngit-email.sh\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Rikka\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "#### 1、说明\n\nfirda gadget 模式支持如下四种模式：\n\n   - Listen（部分支持，不支持 `\"on_load\": \"wait\"`）\n   - Connect (?)\n   - Script （完整支持）\n   - ScriptDirectory（完整支持）\n   \n我没有全部测试，根据使用目的不同，我现在只需要最后一种，主要用于大规模手机部署hook功能，为了把 libgadget.so 注入到进程，所以选择了 magisk + riru 的模式，通过自定义riru模块在riru的回调里面加载 libgadget.so \n\n[Riru-ModuleTemplate](https://github.com/RikkaApps/Riru-ModuleTemplate)\n\n本项目使用的是 [API 10 (Riru v23)](https://github.com/RikkaApps/Riru-ModuleTemplate#api-10-riru-v23) 来编译的\n\n\n#### 2、 目的 & 功能\n\n- frida 持久化\n- frida 代码能够hook同一个应用的不同进程\n- 应用白名单（避免和其他hook框架冲突）\n- 为了用于生产环境而不是调试环境\n\n#### 3、适配Android版本\n\nAndroid 9，Android 10 ，？\n\n#### 4、安装\n\n- 通过 twrp 刷入 magisk v21 - v22.1(或者官网推荐方式)\n- 通过 magisk 刷入 riru ，目前测试过 v23.9 ~ v25.4.4 \n- 通过 magisk 刷入 riru-FridaGadgetRiruMoudle-v14.2.12.9.zip\n- 记得重启手机\n\n#### 5、编译 \n\ngradle assembleRelease\n\n#### 6、配置\n\n##### 6.1、白名单\n\n`/data/local/tmp/_white_list.config`\n\n我写的这个白名单是控制进程是不是需要加载 gadget 的so的，目的是为了在手机上同时兼容xposed，要不然一个进程启动的时候同时加载 xposed 的so和 gadget.so  这时候进程会卡死。\n\n这个配置文件格式很简单，就是把app进程名(包名)用逗号隔开，比如：\n\n```txt\ncom.twitter.twitter,com.github.testapp1,com.github.testapp2\n```\n\n##### 6.2、gadget scriptdirectory 配置\n\n这个配置不同于上面的白名单配置，这个配置是用了控制当前已经加载了 gadget.so 的进程，是不是要加载和当前配置文件同名的js文件的。\n\nhttps://frida.re/docs/gadget/#scriptdirectory\n\n我把 gadget 的这个识别的目录硬编码指定在了 `/data/local/tmp/frida_scripts` 如果有需要，可以修改后自己编译当前项目。\n\n那么以twitter 手机目录结构如下为例：\n```\n/data/local/tmp/_white_list.config\n/data/local/tmp/frida_scripts/twitter.js\n/data/local/tmp/frida_scripts/twitter.config\n```\n\ntwitter.config  配置文件的目的是为了指定是否应该为某个 app 加载 twitter.js hook 脚本。twitter.js 就是普通的frida hook 脚本，twitter.config 文件格式大概如下：\n\n```json\n{\n  \"filter\": {\n    \"executables\": [\"com.twitter.twitter\"],\n    \"bundles\": [],\n    \"objc_classes\": []\n  }\n}\n```\n\n#### 7、TODO \n\n1、构建用于调试的工具 \n\n2、开发一个图形界面用于配置配置文件和传输js脚本\n\n3、app 图形界面控制gadget的动态库可选\n\n4、整理当前ts 脚本，分离改机和系统函数监控\n"
  },
  {
    "path": "build.gradle",
    "content": "apply plugin: 'idea'\n\nidea.module {\n    excludeDirs += file('out')\n    resourceDirs += file('template')\n    resourceDirs += file('scripts')\n}\n\nbuildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.1.1'\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n\next {\n    minSdkVersion = 23\n    targetSdkVersion = 30\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Oct 09 23:12:33 CST 2020\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.6.1-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=-Xmx1536m\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# https://github.com/google/prefab/issues/122\nandroid.prefabVersion=1.1.2"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\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=\"\"\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# 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, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\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=$((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\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "module/.gitignore",
    "content": "/.externalNativeBuild\n/build\n/release"
  },
  {
    "path": "module/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply from: file(rootProject.file('module.gradle'))\n\nandroid {\n    compileSdkVersion rootProject.ext.targetSdkVersion\n    defaultConfig {\n        minSdkVersion rootProject.ext.minSdkVersion\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        externalNativeBuild {\n            cmake {\n                arguments \"-DMODULE_NAME:STRING=riru_$moduleId\",\n                        \"-DRIRU_MODULE_API_VERSION=$moduleRiruApiVersion\",\n                        \"-DRIRU_MODULE_VERSION=$moduleVersionCode\",\n                        \"-DRIRU_MODULE_VERSION_NAME:STRING=\\\"$moduleVersion\\\"\"\n\n            }\n        }\n    }\n    buildFeatures {\n        prefab true\n    }\n    externalNativeBuild {\n        cmake {\n            path \"src/main/cpp/CMakeLists.txt\"\n            version \"3.10.2\"\n        }\n    }\n    ndkVersion '22.0.6917172 rc1'\n}\n\nrepositories {\n    mavenLocal()\n    jcenter()\n    maven { url 'https://dl.bintray.com/rikkaw/Libraries' }\n\n    flatDir { dirs 'libs' }\n}\n\ndependencies {\n    // This is prefab aar which contains \"riru.h\"\n    // If you want to use older versions of AGP,\n    // you can copy this file from https://github.com/RikkaApps/Riru/blob/master/riru/src/main/cpp/include_riru/riru.h\n\n    // The default version of prefab in AGP has problem to process header only package,\n    // you may have to add android.prefabVersion=1.1.2 in your gradle.properties.\n    // See https://github.com/google/prefab/issues/122\n\n//    implementation 'rikka.ndk:riru:10'\n    implementation (name:'riru-10', ext:'aar')\n}\n\ndef outDir = file(\"$rootDir/out\")\ndef magiskDir = file(\"$outDir/magisk_module\")\ndef zipName = \"${magiskModuleProp['id'].replace('_', '-')}-${magiskModuleProp['version']}.zip\"\ndef riruDir = \"$magiskDir/riru\"\n\n\nimport org.apache.tools.ant.filters.FixCrLfFilter\n\nimport java.nio.file.Files\nimport java.security.MessageDigest\n\nstatic def calcSha256(file) {\n    def md = MessageDigest.getInstance(\"SHA-256\")\n    file.eachByte 4096, { bytes, size ->\n        md.update(bytes, 0, size);\n    }\n    return md.digest().encodeHex()\n}\n\n\nstatic def renameOrFail(File fromFile, File toFile) {\n    if (!fromFile.renameTo(toFile)) {\n        throw new IOException(\"Unable reanme file $fromFile to $toFile\")\n    }\n}\n\nandroid.libraryVariants.all { variant ->\n    def task = variant.assembleProvider.get()\n    task.doLast {\n        // clear\n        delete { delete magiskDir }\n\n        // copy from template\n        copy {\n            from \"$rootDir/template/magisk_module\"\n            into magiskDir.path\n            exclude 'riru.sh'\n        }\n        // copy riru.sh\n        copy {\n            from \"$rootDir/template/magisk_module\"\n            into magiskDir.path\n            include 'riru.sh'\n            filter { line ->\n                line.replaceAll('%%%RIRU_MODULE_ID%%%', moduleId)\n                        .replaceAll('%%%RIRU_MODULE_API_VERSION%%%', moduleRiruApiVersion.toString())\n                        .replaceAll('%%%RIRU_MODULE_MIN_API_VERSION%%%', moduleMinRiruApiVersion.toString())\n                        .replaceAll('%%%RIRU_MODULE_MIN_RIRU_VERSION_NAME%%%', moduleMinRiruVersionName)\n            }\n            filter(FixCrLfFilter.class,\n                    eol: FixCrLfFilter.CrLf.newInstance(\"lf\"))\n        }\n        // copy .git files manually since gradle exclude it by default\n        Files.copy(file(\"$rootDir/template/magisk_module/.gitattributes\").toPath(), file(\"${magiskDir.path}/.gitattributes\").toPath())\n\n        // generate module.prop\n        def modulePropText = \"\"\n        magiskModuleProp.each { k, v -> modulePropText += \"$k=$v\\n\" }\n        modulePropText = modulePropText.trim()\n        file(\"$magiskDir/module.prop\").text = modulePropText\n\n        // generate module.prop for Riru\n        def riruModulePropText = \"\"\n        moduleProp.each { k, v -> riruModulePropText += \"$k=$v\\n\" }\n        riruModulePropText = riruModulePropText.trim()\n        file(riruDir).mkdirs()\n\n        // module.prop.new will be renamed to module.prop in post-fs-data.sh\n        file(\"$riruDir/module.prop.new\").text = riruModulePropText\n\n        // copy native files\n        def nativeOutDir = file(\"build/intermediates/cmake/$variant.name/obj\")\n\n        file(\"$magiskDir/system\").mkdirs()\n        file(\"$magiskDir/system_x86\").mkdirs()\n\n        if (file(\"$magiskDir/system/lib64\").exists()) {\n            copy {\n                from \"$nativeOutDir/arm64-v8a\"\n                into \"$magiskDir/system/lib64\"\n                include '*.so'\n                include '*.sha256sum'\n            }\n        } else {\n            renameOrFail(file(\"$nativeOutDir/arm64-v8a\"), file(\"$magiskDir/system/lib64\"))\n        }\n        if (file(\"$magiskDir/system/lib\").exists()) {\n            copy {\n                from \"$nativeOutDir/armeabi-v7a\"\n                into \"$magiskDir/system/lib\"\n                include '*.so'\n            }\n        } else {\n            renameOrFail(file(\"$nativeOutDir/armeabi-v7a\"), file(\"$magiskDir/system/lib\"))\n        }\n\n        renameOrFail(file(\"$nativeOutDir/x86_64\"), file(\"$magiskDir/system_x86/lib64\"))\n        renameOrFail(file(\"$nativeOutDir/x86\"), file(\"$magiskDir/system_x86/lib\"))\n\n        // generate sha1sum\n        fileTree(\"$magiskDir\").matching {\n            exclude \"README.md\", \"META-INF\"\n        }.visit { f ->\n            if (f.directory) return\n            file(f.file.path + \".sha256sum\").text = calcSha256(f.file)\n        }\n    }\n    task.finalizedBy zipMagiskMoudle\n}\n\ntask zipMagiskMoudle(type: Zip) {\n    from magiskDir\n    archiveName zipName\n    destinationDir outDir\n}"
  },
  {
    "path": "module/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"riru.template\" />\n"
  },
  {
    "path": "module/src/main/cpp/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.4.1)\n\nif (NOT DEFINED MODULE_NAME)\n    message(FATAL_ERROR \"MODULE_NAME is not set\")\nendif ()\n\nadd_definitions(-DRIRU_MODULE)\nadd_definitions(-DRIRU_MODULE_API_VERSION=${RIRU_MODULE_API_VERSION})\nadd_definitions(-DRIRU_MODULE_VERSION=${RIRU_MODULE_VERSION})\nadd_definitions(-DRIRU_MODULE_VERSION_NAME=${RIRU_MODULE_VERSION_NAME})\n\nmessage(\"Build type: ${CMAKE_BUILD_TYPE}\")\n\nset(CMAKE_CXX_STANDARD 11)\n\nset(LINKER_FLAGS \"-ffixed-x18 -Wl,--hash-style=both\")\nset(C_FLAGS \"-Werror=format -fdata-sections -ffunction-sections\")\n\nif (CMAKE_BUILD_TYPE STREQUAL \"Release\")\n    set(C_FLAGS \"${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden\")\n    set(LINKER_FLAGS \"${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections\")\nelse ()\n    set(C_FLAGS \"${C_FLAGS} -O0\")\nendif ()\n\nset(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} ${C_FLAGS}\")\nset(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} ${C_FLAGS}\")\n\nset(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}\")\nset(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}\")\n\nfind_package(riru REQUIRED CONFIG)\n\nadd_library(${MODULE_NAME} SHARED main.cpp)\ntarget_link_libraries(${MODULE_NAME} log riru::riru)\nset_target_properties(${MODULE_NAME} PROPERTIES LINK_FLAGS_RELEASE -s)\n"
  },
  {
    "path": "module/src/main/cpp/logging.h",
    "content": "#ifndef _LOGGING_H\n#define _LOGGING_H\n\n#include <errno.h>\n#include \"android/log.h\"\n\n#ifndef LOG_TAG\n#define LOG_TAG    \"Riru-ModuleFridaGadget\"\n#endif\n\n#ifdef DEBUG\n#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)\n#else\n#define LOGD(...)\n#endif\n#define LOGV(...)  __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)\n#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)\n#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)\n#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)\n#define PLOGE(fmt, args...) LOGE(fmt \" failed with %d: %s\", ##args, errno, strerror(errno))\n\n#endif // _LOGGING_H"
  },
  {
    "path": "module/src/main/cpp/main.cpp",
    "content": "#include <jni.h>\n#include <sys/types.h>\n#include <riru.h>\n#include <malloc.h>\n#include <vector>\n\n#include <cstring>\n#include <dlfcn.h>\n#include \"logging.h\"\n#include \"main.h\"\n\nstatic char nice_process_name[256] = {0};\nstatic char package_name[256] = {0};\n\nstatic jint my_uid = 0;\n\nstatic bool isApp(int uid) {\n    if (uid < 0) {\n        return false;\n    }\n    int appId = uid % 100000;\n\n    // limit only regular app, or strange situation will happen, such as zygote process not start (dead for no reason and leave no clues?)\n    // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r8/core/java/android/os/UserHandle.java#151\n    return appId >= 10000 && appId <= 19999;\n}\n\nstatic void\nmy_forkAndSpecializePre(JNIEnv *env, jint *uid, jstring *niceName, jstring *appDataDir) {\n    //LOGI(\"Q_M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx %s\", \"forkAndSpecializePre\");\n    my_uid = *uid;\n    if (!isApp(my_uid)) {\n        return;\n    }\n\n    const char *tablePath = (env->GetStringUTFChars(*niceName, 0));\n    sprintf(nice_process_name, \"%s\", tablePath);\n    delete tablePath;\n\n    if (!appDataDir) {\n        LOGI(\"Q_M forkAndSpecializePre appDataDir null\");\n        return;\n    }\n\n    const char *app_data_dir = env->GetStringUTFChars(*appDataDir, NULL);\n    if (app_data_dir == nullptr) {\n        return;\n    }\n    //LOGI(\"Q_M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx app_data_dir %s\",app_data_dir);\n\n    int user = 0;\n    if (sscanf(app_data_dir, \"/data/%*[^/]/%d/%s\", &user, package_name) != 2) {\n        if (sscanf(app_data_dir, \"/data/%*[^/]/%s\", package_name) != 1) {\n            package_name[0] = '\\0';\n            LOGI(\"Q_M can't parse %s\", app_data_dir);\n        }\n    }\n    env->ReleaseStringUTFChars(*appDataDir, app_data_dir);\n\n}\n\nstatic void my_forkAndSpecializePost(JNIEnv *env) {\n    if (!isApp(my_uid)) {\n        return;\n    }\n\n//    if (!strstr(nice_process_name, \"com.smile.gifmaker\")\n//        && !strstr(nice_process_name, \"com.ss.android.ugc.aweme\")\n//        && !strstr(nice_process_name, \"com.xingin.xhs\")\n//            ) {\n//        return;\n//    }\n\n    //http://www.cplusplus.com/reference/cstdio/fread/ 读取整个文件\n    char *white_list;\n    //白名单的pkgName 最好以逗号或者分好分割开来\n    const char *filepath = \"/data/local/tmp/_white_list.config\";\n    FILE *fp = nullptr;\n    fp = fopen(filepath, \"r\");\n    if (fp != nullptr) {\n\n        fseek(fp, 0, SEEK_END);\n        int fileLen = ftell(fp);\n        white_list = (char *) malloc(sizeof(char) * (fileLen + 1));\n        fseek(fp, 0, SEEK_SET);\n//        size_t count = fread(white_list, fileLen, sizeof(char), fp);\n        size_t count = fread(white_list, 1, fileLen, fp);\n//        LOGI(\"Q_M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 白名单长度 %zu\", count);\n        white_list[count] = '\\0';\n        fclose(fp);\n\n        LOGI(\"Q_M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 白名单：%s\", white_list);\n    } else {\n        white_list = \"\";\n    }\n\n    if (!strstr(white_list, package_name)) {\n        return;\n    }\n\n    LOGI(\"Q_M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx nice_process_name=%s, pkg=%s,uid=%d, isApp= %d\",\n         nice_process_name, package_name, my_uid,\n         isApp(my_uid));\n\n    //添加这种机制，就可以提前设置进程名， 从而让frida 的gadget 能够识别到\n    jclass java_Process = env->FindClass(\"android/os/Process\");\n    if (java_Process != nullptr && isApp(my_uid)) {\n        jmethodID mtd_setArgV0 = env->GetStaticMethodID(java_Process, \"setArgV0\",\n                                                        \"(Ljava/lang/String;)V\");\n        jstring name = env->NewStringUTF(nice_process_name);\n        env->CallStaticVoidMethod(java_Process, mtd_setArgV0, name);\n\n        void *handle = dlopen(nextLoadSo, RTLD_LAZY);\n        if (!handle) {\n            //        LOGE(\"%s\",dlerror());\n            LOGE(\"Q_M  %s loaded in libgadget 出错 %s\", nice_process_name, dlerror());\n        } else {\n            LOGI(\"Q_M xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-> %s 加载 ' %s ' 成功 \", nice_process_name,\n                 nextLoadSo);\n        }\n    }\n}\n\n\nstatic void forkAndSpecializePre(\n        JNIEnv *env, jclass clazz, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,\n        jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,\n        jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote,\n        jstring *instructionSet, jstring *appDataDir, jboolean *isTopApp,\n        jobjectArray *pkgDataInfoList,\n        jobjectArray *whitelistedDataInfoList, jboolean *bindMountAppDataDirs,\n        jboolean *bindMountAppStorageDirs) {\n    my_forkAndSpecializePre(env, uid, niceName, appDataDir);\n}\n\n//很遗憾，执行这行代码的时候，还没有设置进程名字，导致，这个方法里面加载 frida gadget 的动态库获取不到进程名\n// 只有执行完 Zygote.forkAndSpecialize 会在 handleChildProc 里面设置进程名，Process.setArgV0(parsedArgs.niceName);\n// libandroid_runtime.so\n//https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_util_Process.cpp;l=593?q=setSwappiness&ss=android%2Fplatform%2Fsuperproject\nstatic void forkAndSpecializePost(JNIEnv *env, jclass clazz, jint res) {\n    if (res == 0) {\n        my_forkAndSpecializePost(env);\n    } else {\n        // in zygote process, res is child pid\n        // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66\n    }\n}\n\nstatic void specializeAppProcessPre(\n        JNIEnv *env, jclass clazz, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,\n        jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,\n        jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir,\n        jboolean *isTopApp, jobjectArray *pkgDataInfoList, jobjectArray *whitelistedDataInfoList,\n        jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs) {\n    // added from Android 10, but disabled at least in Google Pixel devices\n\n    my_forkAndSpecializePre(env, uid, niceName, appDataDir);\n}\n\nstatic void specializeAppProcessPost(\n        JNIEnv *env, jclass clazz) {\n    // added from Android 10, but disabled at least in Google Pixel devices\n    my_forkAndSpecializePost(env);\n}\n\nstatic void forkSystemServerPre(\n        JNIEnv *env, jclass clazz, uid_t *uid, gid_t *gid, jintArray *gids, jint *runtimeFlags,\n        jobjectArray *rlimits, jlong *permittedCapabilities, jlong *effectiveCapabilities) {\n\n}\n\nstatic void forkSystemServerPost(JNIEnv *env, jclass clazz, jint res) {\n    if (res == 0) {\n        // in system server process\n    } else {\n        // in zygote process, res is child pid\n        // don't print log here, see https://github.com/RikkaApps/Riru/blob/77adfd6a4a6a81bfd20569c910bc4854f2f84f5e/riru-core/jni/main/jni_native_method.cpp#L55-L66\n    }\n}\n\nstatic int shouldSkipUid(int uid) {\n    // By default (the module does not provide this function in init), Riru will only call\n    // module functions in \"normal app processes\" (10000 <= uid % 100000 <= 19999)\n\n    // Provide this function so that the module can control if a specific uid should be skipped\n    return false;\n}\n\nstatic void onModuleLoaded() {\n    // called when the shared library of Riru core is loaded\n}\n\nextern \"C\" {\n\nint riru_api_version;\nRiruApiV9 *riru_api_v9;\n\n/*\n * Init will be called three times.\n *\n * The first time:\n *   Returns the highest version number supported by both Riru and the module.\n *\n *   arg: (int *) Riru's API version\n *   returns: (int *) the highest possible API version\n *\n * The second time:\n *   Returns the RiruModuleX struct created by the module.\n *   (X is the return of the first call)\n *\n *   arg: (RiruApiVX *) RiruApi strcut, this pointer can be saved for further use\n *   returns: (RiruModuleX *) RiruModule strcut\n *\n * The third time:\n *   Let the module to cleanup (such as RiruModuleX struct created before).\n *\n *   arg: null\n *   returns: (ignored)\n *\n */\nvoid *init(void *arg) {\n    static int step = 0;\n    step += 1;\n    LOGI(\"Q_M Riru-ModuleFridaGadget xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx %s\", \"init\");\n\n    static void *_module;\n\n    switch (step) {\n        case 1: {\n            auto core_max_api_version = *(int *) arg;\n            riru_api_version =\n                    core_max_api_version <= RIRU_MODULE_API_VERSION ? core_max_api_version\n                                                                    : RIRU_MODULE_API_VERSION;\n            return &riru_api_version;\n        }\n        case 2: {\n            switch (riru_api_version) {\n                // RiruApiV10 and RiruModuleInfoV10 are equal to V9\n                case 10:\n                case 9: {\n                    riru_api_v9 = (RiruApiV9 *) arg;\n\n                    auto module = (RiruModuleInfoV9 *) malloc(sizeof(RiruModuleInfoV9));\n                    memset(module, 0, sizeof(RiruModuleInfoV9));\n                    _module = module;\n\n                    module->supportHide = true;\n\n                    module->version = RIRU_MODULE_VERSION;\n                    module->versionName = RIRU_MODULE_VERSION_NAME;\n                    module->onModuleLoaded = onModuleLoaded;\n                    module->shouldSkipUid = shouldSkipUid;\n                    module->forkAndSpecializePre = forkAndSpecializePre;\n                    module->forkAndSpecializePost = forkAndSpecializePost;\n                    module->specializeAppProcessPre = specializeAppProcessPre;\n                    module->specializeAppProcessPost = specializeAppProcessPost;\n                    module->forkSystemServerPre = forkSystemServerPre;\n                    module->forkSystemServerPost = forkSystemServerPost;\n                    return module;\n                }\n                default: {\n                    return nullptr;\n                }\n            }\n        }\n        case 3: {\n            free(_module);\n            return nullptr;\n        }\n        default: {\n            return nullptr;\n        }\n    }\n}\n}"
  },
  {
    "path": "module/src/main/cpp/main.h",
    "content": "//\n// Created by canyie on 2020/8/12.\n//\n\n#ifndef NBINJECTION_MAIN_H\n#define NBINJECTION_MAIN_H\n\n\n#ifdef __LP64__\nconstexpr const char* kZygoteNiceName = \"zygote64\";\nconstexpr const char* nextLoadSo = \"/system/lib64/libgadget.so\";\n#else\nconstexpr const char* kZygoteNiceName = \"zygote\";\nconstexpr const char* nextLoadSo = \"/system/lib/libgadget.so\";\n#endif\n\n#endif //NBINJECTION_MAIN_H\n"
  },
  {
    "path": "module.example.gradle",
    "content": "ext {\n    // FIXME replace with yours\n    moduleId = \"template\"\n    moduleName = \"Template\"\n    moduleAuthor = \"Template\"\n    moduleDescription = \"Riru module template.\"\n    moduleVersion = \"v1.1\"\n    moduleVersionCode = 1\n\n    moduleMinRiruApiVersion = 9\n    moduleMinRiruVersionName = \"v22.0\"\n    moduleRiruApiVersion = 10\n\n    moduleProp = [\n            name       : moduleName,\n            version    : moduleVersion,\n            versionCode: moduleVersionCode.toString(),\n            author     : moduleAuthor,\n            description: moduleDescription,\n            minApi     : moduleMinRiruApiVersion\n    ]\n\n    magiskModuleProp = [\n            id         : \"riru-${moduleId.replace('_', '-')}\",\n            name       : \"Riru - ${moduleProp['name']}\",\n            version    : moduleProp['version'],\n            versionCode: moduleProp['versionCode'],\n            author     : moduleProp['author'],\n            description: moduleProp['description']\n    ]\n}"
  },
  {
    "path": "module.gradle",
    "content": "ext {\n    moduleId = \"FridaGadgetRiruMoudle\"\n    moduleName = \"Frida-Gadget-Riru-Module\"\n    moduleAuthor = \"BayBayMax\"\n    moduleDescription = \"一个加载 FridaGadget 动态库的magisk 插件，依赖riru 框架\"\n    moduleVersion = \"v14.2.12.9\"\n    moduleVersionCode = 14_2_12_9\n\n    moduleMinRiruApiVersion = 9\n    moduleMinRiruVersionName = \"v22.0\"\n    moduleRiruApiVersion = 10\n\n    moduleProp = [\n            name       : moduleName,\n            version    : moduleVersion,\n            versionCode: moduleVersionCode.toString(),\n            author     : moduleAuthor,\n            description: moduleDescription,\n            minApi     : moduleMinRiruApiVersion\n    ]\n\n    magiskModuleProp = [\n            id         : \"riru-${moduleId.replace('_', '-')}\",\n            name       : \"Riru - ${moduleProp['name']}\",\n            version    : moduleProp['version'],\n            versionCode: moduleProp['versionCode'],\n            author     : moduleProp['author'],\n            description: moduleProp['description']\n    ]\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':module'\n"
  },
  {
    "path": "template/magisk_module/.gitattributes",
    "content": "# Declare files that will always have LF line endings on checkout.\nMETA-INF/** text eol=lf\n*.prop text eol=lf\n*.sh text eol=lf\n*.md text eol=lf\nsepolicy.rule text eol=lf\n\n# Denote all files that are truly binary and should not be modified.\nsystem/** binary\nsystem_x86/** binary"
  },
  {
    "path": "template/magisk_module/META-INF/com/google/android/update-binary",
    "content": "#!/sbin/sh\n\n#################\n# Initialization\n#################\n\numask 022\n\n# Global vars\nTMPDIR=/dev/tmp\nPERSISTDIR=/sbin/.magisk/mirror/persist\n\nrm -rf $TMPDIR 2>/dev/null\nmkdir -p $TMPDIR\n\n# echo before loading util_functions\nui_print() { echo \"$1\"; }\n\nrequire_new_magisk() {\n  ui_print \"*******************************\"\n  ui_print \" Please install Magisk v19.0+! \"\n  ui_print \"*******************************\"\n  exit 1\n}\n\nis_legacy_script() {\n  unzip -l \"$ZIPFILE\" install.sh | grep -q install.sh\n  return $?\n}\n\nprint_modname() {\n  local len\n  len=`echo -n $MODNAME | wc -c`\n  len=$((len + 2))\n  local pounds=`printf \"%${len}s\" | tr ' ' '*'`\n  ui_print \"$pounds\"\n  ui_print \" $MODNAME \"\n  ui_print \"$pounds\"\n  ui_print \"*******************\"\n  ui_print \" Powered by Magisk \"\n  ui_print \"*******************\"\n}\n\n##############\n# Environment\n##############\n\nOUTFD=$2\nZIPFILE=$3\n\nmount /data 2>/dev/null\n\n# Load utility functions\n[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk\n. /data/adb/magisk/util_functions.sh\n[ $MAGISK_VER_CODE -gt 18100 ] || require_new_magisk\n\n# Preperation for flashable zips\nsetup_flashable\n\n# Mount partitions\nmount_partitions\n\n# Detect version and architecture\napi_level_arch_detect\n\n# Setup busybox and binaries\n$BOOTMODE && boot_actions || recovery_actions\n\n##############\n# Preparation\n##############\n\n# Extract prop file\nunzip -o \"$ZIPFILE\" module.prop -d $TMPDIR >&2\n[ ! -f $TMPDIR/module.prop ] && abort \"! Unable to extract zip file!\"\n\n$BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules\nMODULEROOT=$NVBASE/$MODDIRNAME\nMODID=`grep_prop id $TMPDIR/module.prop`\nMODPATH=$MODULEROOT/$MODID\nMODNAME=`grep_prop name $TMPDIR/module.prop`\n\n# Create mod paths\nrm -rf $MODPATH 2>/dev/null\nmkdir -p $MODPATH\n\n##########\n# Install\n##########\n\nif is_legacy_script; then\n  unzip -oj \"$ZIPFILE\" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2\n\n  # Load install script\n  . $TMPDIR/install.sh\n\n  # Callbacks\n  print_modname\n  on_install\n\n  # Custom uninstaller\n  [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh\n\n  # Skip mount\n  $SKIPMOUNT && touch $MODPATH/skip_mount\n\n  # prop file\n  $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop\n\n  # Module info\n  cp -af $TMPDIR/module.prop $MODPATH/module.prop\n\n  # post-fs-data scripts\n  $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh\n\n  # service scripts\n  $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh\n\n  ui_print \"- Setting permissions\"\n  set_permissions\nelse\n  print_modname\n\n  unzip -o \"$ZIPFILE\" customize.sh -d $MODPATH >&2\n\n  if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then\n    ui_print \"- Extracting module files\"\n    unzip -o \"$ZIPFILE\" -x 'META-INF/*' -d $MODPATH >&2\n\n    # Default permissions\n    set_perm_recursive $MODPATH 0 0 0755 0644\n  fi\n\n  # Load customization script\n  [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh\nfi\n\n# Handle replace folders\nfor TARGET in $REPLACE; do\n  ui_print \"- Replace target: $TARGET\"\n  mktouch $MODPATH$TARGET/.replace\ndone\n\nif $BOOTMODE; then\n  # Update info for Magisk Manager\n  mktouch $NVBASE/modules/$MODID/update\n  cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop\nfi\n\n# Copy over custom sepolicy rules\nif [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then\n  ui_print \"- Installing custom sepolicy patch\"\n  PERSISTMOD=$PERSISTDIR/magisk/$MODID\n  mkdir -p $PERSISTMOD\n  cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule\nfi\n\n# Remove stuffs that don't belong to modules\nrm -rf \\\n$MODPATH/system/placeholder $MODPATH/customize.sh \\\n$MODPATH/README.md $MODPATH/.git* 2>/dev/null\n\n##############\n# Finalizing\n##############\n\ncd /\n$BOOTMODE || recovery_cleanup\nrm -rf $TMPDIR\n\nui_print \"- Done\"\nexit 0"
  },
  {
    "path": "template/magisk_module/META-INF/com/google/android/updater-script",
    "content": "#MAGISK\n"
  },
  {
    "path": "template/magisk_module/README.md",
    "content": "# Riru - Template"
  },
  {
    "path": "template/magisk_module/customize.sh",
    "content": "SKIPUNZIP=1\n\n# check_architecture\nif [ \"$ARCH\" != \"arm\" ] && [ \"$ARCH\" != \"arm64\" ] && [ \"$ARCH\" != \"x86\" ] && [ \"$ARCH\" != \"x64\" ]; then\n  abort \"! Unsupported platform: $ARCH\"\nelse\n  ui_print \"- Device platform: $ARCH\"\nfi\n\n# extract verify.sh\nui_print \"- Extracting verify.sh\"\nunzip -o \"$ZIPFILE\" 'verify.sh' -d \"$TMPDIR\" >&2\nif [ ! -f \"$TMPDIR/verify.sh\" ]; then\n  ui_print    \"*********************************************************\"\n  ui_print    \"! Unable to extract verify.sh!\"\n  ui_print    \"! This zip may be corrupted, please try downloading again\"\n  abort \"*********************************************************\"\nfi\n. $TMPDIR/verify.sh\n\n# extract riru.sh\nextract \"$ZIPFILE\" 'riru.sh' \"$MODPATH\"\n. $MODPATH/riru.sh\n\ncheck_riru_version\n\n# extract libs\nui_print \"- Extracting module files\"\n\nextract \"$ZIPFILE\" 'module.prop' \"$MODPATH\"\nextract \"$ZIPFILE\" 'post-fs-data.sh' \"$MODPATH\"\nextract \"$ZIPFILE\" 'uninstall.sh' \"$MODPATH\"\n\n\nif [ \"$ARCH\" = \"x86\" ] || [ \"$ARCH\" = \"x64\" ]; then\n  ui_print \"- Extracting x86 libraries\"\n  extract \"$ZIPFILE\" \"system_x86/lib/libriru_$RIRU_MODULE_ID.so\" \"$MODPATH\"\n  mv \"$MODPATH/system_x86\" \"$MODPATH/system\"\n\n  if [ \"$IS64BIT\" = true ]; then\n    ui_print \"- Extracting x64 libraries\"\n    extract \"$ZIPFILE\" \"system_x86/lib64/libriru_$RIRU_MODULE_ID.so\" \"$MODPATH\"\n    mv \"$MODPATH/system_x86/lib64\" \"$MODPATH/system/lib64\"\n  fi\nelse\n  ui_print \"- Extracting arm libraries\"\n\n  REPLACEMENT_DIR=\"system/*\"\n  # 一劳永逸。完全提取。在某些一加手机上这个方法不能提取 system/lib/ 里面的数据，为啥？是unzip这个命令工具的问题。\n  # 注意加上引号\n  unzip -o \"$ZIPFILE\" \"$REPLACEMENT_DIR\" -d \"$MODPATH\" >&2 || abort \"! Can't extract system/: $?\"\n\n#  extract \"$ZIPFILE\" \"system/lib/libgadget.so\" \"$MODPATH\"\n#  extract \"$ZIPFILE\" \"system/lib/libgadget.config.so\" \"$MODPATH\"\n#  extract \"$ZIPFILE\" \"system/lib/libriru_FridaGadgetRiruMoudle.so\" \"$MODPATH\"\n#\n#  extract \"$ZIPFILE\" \"system/lib64/libgadget.so\" \"$MODPATH\"\n#  extract \"$ZIPFILE\" \"system/lib64/libgadget.config.so\" \"$MODPATH\"\n#  extract \"$ZIPFILE\" \"system/lib64/libriru_FridaGadgetRiruMoudle.so\" \"$MODPATH\"\n\nfi\n\nset_perm_recursive \"$MODPATH\" 0 0 0755 0644\n\n# extract Riru files\nui_print \"- Extracting extra files\"\n[ -d \"$RIRU_MODULE_PATH\" ] || mkdir -p \"$RIRU_MODULE_PATH\" || abort \"! Can't create $RIRU_MODULE_PATH\"\n\nrm -f \"$RIRU_MODULE_PATH/module.prop.new\"\nextract \"$ZIPFILE\" 'riru/module.prop.new' \"$RIRU_MODULE_PATH\" true\nset_perm \"$RIRU_MODULE_PATH/module.prop.new\" 0 0 0600 $RIRU_SECONTEXT"
  },
  {
    "path": "template/magisk_module/post-fs-data.sh",
    "content": "#!/system/bin/sh\nMODDIR=${0%/*}\n[ ! -f \"$MODDIR/riru.sh\" ] && exit 1\n. $MODDIR/riru.sh\n\n# Rename module.prop.new\nif [ -f \"$RIRU_MODULE_PATH/module.prop.new\" ]; then\n    rm \"$RIRU_MODULE_PATH/module.prop\"\n    mv \"$RIRU_MODULE_PATH/module.prop.new\" \"$RIRU_MODULE_PATH/module.prop\"\nfi"
  },
  {
    "path": "template/magisk_module/riru.sh",
    "content": "#!/sbin/sh\nRIRU_PATH=\"/data/adb/riru\"\nRIRU_MODULE_ID=\"%%%RIRU_MODULE_ID%%%\"\nRIRU_MODULE_PATH=\"$RIRU_PATH/modules/$RIRU_MODULE_ID\"\nRIRU_SECONTEXT=\"u:object_r:magisk_file:s0\"\n\n# used by /data/adb/riru/util_functions.sh\nRIRU_MODULE_API_VERSION=%%%RIRU_MODULE_API_VERSION%%%\nRIRU_MODULE_MIN_API_VERSION=%%%RIRU_MODULE_MIN_API_VERSION%%%\nRIRU_MODULE_MIN_RIRU_VERSION_NAME=\"%%%RIRU_MODULE_MIN_RIRU_VERSION_NAME%%%\"\n\n# this function will be used when /data/adb/riru/util_functions.sh not exits\ncheck_riru_version() {\n  if [ ! -f \"$RIRU_PATH/api_version\" ] && [ ! -f \"$RIRU_PATH/api_version.new\" ]; then\n    ui_print \"*********************************************************\"\n    ui_print \"! Riru $RIRU_MIN_VERSION_NAME or above is required\"\n    ui_print \"! Please install Riru from Magisk Manager or https://github.com/RikkaApps/Riru/releases\"\n    abort \"*********************************************************\"\n  fi\n  local_api_version=$(cat \"$RIRU_PATH/api_version.new\") || local_api_version=$(cat \"$RIRU_PATH/api_version\") || local_api_version=0\n  [ \"$local_api_version\" -eq \"$local_api_version\" ] || local_api_version=0\n  ui_print \"- Riru API version: $local_api_version\"\n  if [ \"$local_api_version\" -lt $RIRU_MODULE_MIN_API_VERSION ]; then\n    ui_print \"*********************************************************\"\n    ui_print \"! Riru $RIRU_MIN_VERSION_NAME or above is required\"\n    ui_print \"! Please upgrade Riru from Magisk Manager or https://github.com/RikkaApps/Riru/releases\"\n    abort \"*********************************************************\"\n  fi\n}\n\nif [ -f /data/adb/riru/util_functions.sh ]; then\n  ui_print \"- Load /data/adb/riru/util_functions.sh\"\n  . /data/adb/riru/util_functions.sh\nelse\n  ui_print \"- Can't find /data/adb/riru/util_functions.sh\"\nfi"
  },
  {
    "path": "template/magisk_module/uninstall.sh",
    "content": "#!/sbin/sh\nMODDIR=${0%/*}\n[ ! -f \"$MODDIR/riru.sh\" ] && exit 1\n. $MODDIR/riru.sh\n\nrm -rf \"$RIRU_MODULE_PATH\""
  },
  {
    "path": "template/magisk_module/verify.sh",
    "content": "TMPDIR_FOR_VERIFY=\"$TMPDIR/.vunzip\"\nmkdir \"$TMPDIR_FOR_VERIFY\"\n\nabort_verify() {\n  ui_print \"*********************************************************\"\n  ui_print \"! $1\"\n  ui_print \"! This zip may be corrupted, please try downloading again\"\n  abort    \"*********************************************************\"\n}\n\n# extract <zip> <file> <target dir> <junk paths>\nextract() {\n  zip=$1\n  file=$2\n  dir=$3\n  junk_paths=$4\n  [ -z \"$junk_paths\" ] && junk_paths=false\n  opts=\"-o\"\n  [ $junk_paths = true ] && opts=\"-oj\"\n\n  file_path=\"\"\n  hash_path=\"\"\n  if [ $junk_paths = true ]; then\n    file_path=\"$dir/$(basename \"$file\")\"\n    hash_path=\"$TMPDIR_FOR_VERIFY/$(basename \"$file\").sha256sum\"\n  else\n    file_path=\"$dir/$file\"\n    hash_path=\"$TMPDIR_FOR_VERIFY/$file.sha256sum\"\n  fi\n\n  unzip $opts \"$zip\" \"$file\" -d \"$dir\" >&2\n  [ -f \"$file_path\" ] || abort_verify \"$file not exists\"\n\n  unzip $opts \"$zip\" \"$file.sha256sum\" -d \"$TMPDIR_FOR_VERIFY\" >&2\n  [ -f \"$hash_path\" ] || abort_verify \"$file.sha256sum not exists\"\n\n  (echo \"$(cat \"$hash_path\")  $file_path\" | sha256sum -c -s -) || abort_verify \"Failed to verify $file\"\n  ui_print \"- Verified $file\" >&1\n}"
  }
]