From a455eec5de8e1aeffdfa6e7cb1b3de51ba082165 Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Sat, 9 Apr 2022 01:37:12 +0800 Subject: [PATCH] Merge new documentation --- README.md | 106 +-- docs/.nojekyll | 0 docs/.vscode/settings.json | 3 + docs/_404.md | 3 + docs/_coverpage.md | 18 + docs/_navbar.md | 17 + docs/_sidebar.md | 23 + docs/about/about.md | 35 + docs/about/changelog.md | 120 +++ docs/about/contacts.md | 11 + docs/about/future.md | 35 + docs/api/document.md | 53 ++ docs/api/home.md | 27 + .../public/BaseFinder-IndexTypeCondition.md | 101 +++ docs/api/public/ComponentTypeFactory.md | 11 + docs/api/public/ConstructorFinder.md | 380 +++++++++ docs/api/public/CurrentClass.md | 41 + docs/api/public/FieldFinder.md | 560 ++++++++++++ docs/api/public/GraphicsTypeFactory.md | 11 + docs/api/public/HookClass.md | 15 + docs/api/public/HookParam.md | 580 +++++++++++++ docs/api/public/InjectYukiHookWithXposed.md | 17 + docs/api/public/LoggerFactory.md | 73 ++ docs/api/public/MethodFinder.md | 656 ++++++++++++++ docs/api/public/ModifierRules.md | 193 +++++ docs/api/public/PackageParam.md | 433 ++++++++++ docs/api/public/PrefsData.md | 59 ++ docs/api/public/ReflectionFactory.md | 383 +++++++++ docs/api/public/VariableTypeFactory.md | 11 + docs/api/public/VariousClass.md | 29 + docs/api/public/ViewTypeFactory.md | 11 + docs/api/public/YukiBaseHooker.md | 27 + docs/api/public/YukiHookAPI.md | 296 +++++++ docs/api/public/YukiHookCreater.md | 694 +++++++++++++++ docs/api/public/YukiHookFactory.md | 125 +++ docs/api/public/YukiHookModulePrefs.md | 309 +++++++ docs/api/public/YukiHookModuleStatus.md | 55 ++ docs/api/public/YukiHookXposedInitProxy.md | 45 + docs/config/api-example.md | 199 +++++ docs/config/api-exception.md | 703 +++++++++++++++ docs/config/api-using.md | 108 +++ docs/config/r8-proguard.md | 28 + docs/config/xposed-using.md | 96 +++ docs/favicon.ico | Bin 0 -> 9662 bytes docs/guide/example.md | 347 ++++++++ docs/guide/home.md | 142 ++++ docs/guide/knowledge.md | 63 ++ docs/guide/quick-start.md | 145 ++++ docs/guide/special-feature.md | 799 ++++++++++++++++++ docs/icon.png | Bin 0 -> 24264 bytes docs/index.html | 88 ++ docs/sw.js | 90 ++ 52 files changed, 8270 insertions(+), 104 deletions(-) create mode 100644 docs/.nojekyll create mode 100644 docs/.vscode/settings.json create mode 100644 docs/_404.md create mode 100644 docs/_coverpage.md create mode 100644 docs/_navbar.md create mode 100644 docs/_sidebar.md create mode 100644 docs/about/about.md create mode 100644 docs/about/changelog.md create mode 100644 docs/about/contacts.md create mode 100644 docs/about/future.md create mode 100644 docs/api/document.md create mode 100644 docs/api/home.md create mode 100644 docs/api/public/BaseFinder-IndexTypeCondition.md create mode 100644 docs/api/public/ComponentTypeFactory.md create mode 100644 docs/api/public/ConstructorFinder.md create mode 100644 docs/api/public/CurrentClass.md create mode 100644 docs/api/public/FieldFinder.md create mode 100644 docs/api/public/GraphicsTypeFactory.md create mode 100644 docs/api/public/HookClass.md create mode 100644 docs/api/public/HookParam.md create mode 100644 docs/api/public/InjectYukiHookWithXposed.md create mode 100644 docs/api/public/LoggerFactory.md create mode 100644 docs/api/public/MethodFinder.md create mode 100644 docs/api/public/ModifierRules.md create mode 100644 docs/api/public/PackageParam.md create mode 100644 docs/api/public/PrefsData.md create mode 100644 docs/api/public/ReflectionFactory.md create mode 100644 docs/api/public/VariableTypeFactory.md create mode 100644 docs/api/public/VariousClass.md create mode 100644 docs/api/public/ViewTypeFactory.md create mode 100644 docs/api/public/YukiBaseHooker.md create mode 100644 docs/api/public/YukiHookAPI.md create mode 100644 docs/api/public/YukiHookCreater.md create mode 100644 docs/api/public/YukiHookFactory.md create mode 100644 docs/api/public/YukiHookModulePrefs.md create mode 100644 docs/api/public/YukiHookModuleStatus.md create mode 100644 docs/api/public/YukiHookXposedInitProxy.md create mode 100644 docs/config/api-example.md create mode 100644 docs/config/api-exception.md create mode 100644 docs/config/api-using.md create mode 100644 docs/config/r8-proguard.md create mode 100644 docs/config/xposed-using.md create mode 100644 docs/favicon.ico create mode 100644 docs/guide/example.md create mode 100644 docs/guide/home.md create mode 100644 docs/guide/knowledge.md create mode 100644 docs/guide/quick-start.md create mode 100644 docs/guide/special-feature.md create mode 100644 docs/icon.png create mode 100644 docs/index.html create mode 100644 docs/sw.js diff --git a/README.md b/README.md index cfee55b8..93f2ac35 100644 --- a/README.md +++ b/README.md @@ -18,116 +18,14 @@ 《ももくり》女主 栗原 雪(Yuki) - 前身为 [开发学习项目](https://github.com/fankes/TMore) 中使用的 Innocent Xposed API,现在重新命名并开源 -# Functions +# Get Started -- Xposed 模块开发
- 自动构建程序可以帮你快速创建一个 Xposed 模块,完全省去配置入口类和 xposed_init 文件。
-- 轻量优雅
- 拥有一套强大、优雅和人性化的 Kotlin Lambda Hook API,可以帮你快速实现 Method、Constructor、Field 的查找以及 Hook。
-- 高效调试
- 拥有丰富的调试日志功能,细到每个 Hook 方法的名称、所在类以及查找耗时,可进行快速调试和排错。
-- 方便移植
- 原生支持 Xposed API 用法,并原生对接 Xposed API,拥有 Xposed API 的 Hook 框架都能快速对接 Yuki Hook API。
-- 支持混淆
- 使用 Yuki Hook API 构建的 Xposed 模块原生支持 R8 压缩优化混淆,混淆不会破坏 Hook 入口点,R8 下无需任何其它配置。
-- 快速上手
- 简单易用,不需要繁琐的配置,不需要十足的开发经验,搭建环境集成依赖即可立即开始使用。 - -# Supports - -以下是 `YukiHookAPI` 支持的 `Hook Framework` 以及 Xposed 框架。 - -| Hook Framework | ST | Describe | -| --------------------------------------------------------- | -- | ---------------------------------------------------------------------------------------- | -| [LSPosed](https://github.com/LSPosed/LSPosed) | ✅ | 多场景下稳定使用 | -| [EdXposed](https://github.com/ElderDrivers/EdXposed) | ☑ | 部分兼容 | -| [Pine](https://github.com/canyie/pine) | ⭕ | 可以使用 | -| [SandHook](https://github.com/asLody/SandHook) | ⭕ | 可以使用 | -| [Whale](https://github.com/asLody/whale) | ⭕ | 需要 [xposed-hook-based-on-whale](https://github.com/WindySha/xposed-hook-based-on-whale) | -| [YAHFA](https://github.com/PAGalaxyLab/YAHFA) | ❗ | 需要自行实现 Xposed API | -| [FastHook](https://github.com/turing-technician/FastHook) | ❗ | 需要自行实现 Xposed API | -| [Epic](https://github.com/tiann/epic) | ❗ | 需要自行对接 [Dexposed](https://github.com/alibaba/dexposed) | -| [TaiChi](https://github.com/taichi-framework/TaiChi) | ⭕ | 可以作为模块使用 | -| [Xposed](https://github.com/rovo89/Xposed) | ❎ | 未测试,不再推荐使用 | +- [点击这里](https://fankes.github.io/YukiHookAPI) 前往文档页面查看更多详细教程和内容。 # Contacts - [点击加入 Telegram 群组](https://t.me/YukiHookAPI) -# Advantage - -以前,我们在构建 Xposed 模块的时候,首先需要在 `assets` 下创建 `xposed_init` 文件。

-然后,将自己的入口类名手动填入文件中,使用 `XposedHelper` 去实现我们的 Hook 逻辑。 - -- 示例如下 - -```kotlin -class MainHook : IXposedHookLoadPackage { - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - if (lpparam.packageName == "com.android.browser") - XposedHelpers.findAndHookMethod( - Activity::class.java.name, - lpparam.classLoader, - "onCreate", - Bundle::class.java, - object : XC_MethodHook() { - override fun beforeHookedMethod(param: MethodHookParam?) { - // Your code here. - } - - override fun afterHookedMethod(param: MethodHookParam?) { - // Your code here. - } - }) - } -} -``` - -自 `Kotlin` 作为 Android 主要开发语言以来,这套 API 用起来确实已经不是很优雅了。

-有没有什么 好用、轻量、优雅 的解决办法呢?

-本着这样的想法,`YukiHookAPI` 诞生了。

-现在,我们只需要编写少量的代码,一切时间开销和花费交给自动化处理。 - -- 示例如下 - -```kotlin -@InjectYukiHookWithXposed -class MainHook : YukiHookXposedInitProxy { - - override fun onHook() = encase { - loadApp(name = "com.android.browser") { - ActivityClass.hook { - injectMember { - method { - name = "onCreate" - param(BundleClass) - } - beforeHook { - // Your code here. - } - afterHook { - // Your code here. - } - } - } - } - } -} -``` - -是的,你没有看错,仅仅就需要这几行代码,就一切安排妥当。

-代码量少,逻辑清晰,借助高效强大的 `YukiHookAPI`,你就可以实现一个非常简单的 Xposed 模块。 - -# Get Started - -- 你可以点击 [快速开始](https://github.com/fankes/YukiHookAPI/wiki#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B),在 `Gradle` 中集成 `YukiHookAPI` 并开始使用。 -- 更多使用教程及 API 文档请 [前往 Wiki 主页](https://github.com/fankes/YukiHookAPI/wiki) 进行查看。 - -# Changelogs - -- [更新日志](https://github.com/fankes/YukiHookAPI/blob/master/CHANGELOG.md) - # Features 如果你喜欢 `YukiHookAPI` 项目,欢迎为此项目贡献你的代码,可以是任何改进的建议以及新增的功能。 diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/.vscode/settings.json b/docs/.vscode/settings.json new file mode 100644 index 00000000..3b664107 --- /dev/null +++ b/docs/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "git.ignoreLimitWarning": true +} \ No newline at end of file diff --git a/docs/_404.md b/docs/_404.md new file mode 100644 index 00000000..146be7d1 --- /dev/null +++ b/docs/_404.md @@ -0,0 +1,3 @@ +# 404 - 没有找到此页面 + + 请尝试重新输入你需要的页面或通过导航前往指定文档页面。 \ No newline at end of file diff --git a/docs/_coverpage.md b/docs/_coverpage.md new file mode 100644 index 00000000..15334267 --- /dev/null +++ b/docs/_coverpage.md @@ -0,0 +1,18 @@ +

+ +
+ +# Yuki Hook API + +> 一个使用 Kotlin 重构的轻量、高效、稳定的 Xposed Hook API + +- 致力于为 Xposed 模块开发更加便捷而打造的零学习成本框架 + +- 轻量优雅 高效调试 + +- 方便移植 快速上手 + +[GitHub](https://github.com/fankes/YukiHookAPI) +[Get Started](#介绍) + +![color](#ffffff) \ No newline at end of file diff --git a/docs/_navbar.md b/docs/_navbar.md new file mode 100644 index 00000000..9127d7c8 --- /dev/null +++ b/docs/_navbar.md @@ -0,0 +1,17 @@ +* 入门 + * [介绍](guide/home) + * [基础知识](guide/knowledge) + * [快速开始](guide/quick-start) + * [用法示例](guide/example) + * [特色功能](guide/special-feature) + +* 配置 + * [API 基本配置](config/api-example) + * [API 异常处理](config/api-exception) + * [R8 与 Proguard 混淆](config/r8-proguard) + +* API 文档 + * [文档介绍](api/home) + * [Public API](api/document) + +* [联系我们](about/contacts) \ No newline at end of file diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000..13fdeafa --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,23 @@ +* 入门 + * [介绍](guide/home) + * [基础知识](guide/knowledge) + * [快速开始](guide/quick-start) + * [用法示例](guide/example) + * [特色功能](guide/special-feature) + +* 配置 + * [API 基本配置](config/api-example) + * [API 异常处理](config/api-exception) + * [作为 Xposed 模块使用的相关配置](config/xposed-using) + * [作为 Hook API 使用的相关配置](config/api-using) + * [R8 与 Proguard 混淆](config/r8-proguard) + +* API 文档 + * [文档介绍](api/home) + * [Public API](api/document) + +* 关于 + * [更新日志](about/changelog) + * [展望未来](about/future) + * [联系我们](about/contacts) + * [关于此文档](about/about) \ No newline at end of file diff --git a/docs/about/about.md b/docs/about/about.md new file mode 100644 index 00000000..7212a5ca --- /dev/null +++ b/docs/about/about.md @@ -0,0 +1,35 @@ +# 关于此文档 + +> 此文档由 [docsify](https://github.com/QingWei-Li/docsify/) 生成。 + +`docsify` 是一个动态生成文档网站的工具。不同于 `GitBook、Hexo` 的地方是它不会生成将 `.md` 转成 `.html` 文件,所有转换工作都是在运行时进行。 + +## License + +[The MIT License (MIT)](https://github.com/fankes/YukiHookAPI/blob/master/LICENSE) + +``` +MIT License + +Copyright (C) 2019-2022 HighCapable + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +版权所有 © 2019-2022 HighCapable \ No newline at end of file diff --git a/docs/about/changelog.md b/docs/about/changelog.md new file mode 100644 index 00000000..1350da52 --- /dev/null +++ b/docs/about/changelog.md @@ -0,0 +1,120 @@ +# 更新日志 + +> 这里记录了 `YukiHookAPI` 的版本更新历史。 + +### 1.0.71 | 2022.04.04 + +- 修复 VariousClass 无法匹配时会停止 Hook 抛出异常的严重问题 + +### 1.0.70 | 2022.04.04 + +- 修复 `instanceClass` 在静态实例中调用后报错问题 +- 在 Hook 过程中加入 `isUseAppClassLoader` 功能,感谢 [WankkoRee](https://github.com/WankkoRee) 的反馈 +- 加入 `withProcess` 功能,可根据 APP 当前指定进程进行 Hook +- 修复查找方法、构造类和变量的严重逻辑错误问题 +- 修复 Hook 目标类不存在的时候无法忽略异常输出的问题 +- 修复部分情况下 APP 启动方法装载过快导致 Hook 不能生效的问题 +- 修复 `allMethods` 未 Hook 到方法时不会抛出异常的问题,感谢 [WankkoRee](https://github.com/WankkoRee) 的反馈 +- 加入 Hook 状态监听功能,感谢 [WankkoRee](https://github.com/WankkoRee) 的建议 +- 修改 Xposed 入口注入类的方式,重新声明 API 的定义域 +- 加入混淆的方法以及变量的查找功能,可使用不同类型筛选 `index` 定位指定的方法和变量,感谢 [WankkoRee](https://github.com/WankkoRee) 提供的思路 +- 查找方法、变量时允许传入多种类型,例如 `String` 声明的类名和 `VariousClass` +- 加入全新的 `current` 功能,可对任意的类构建一个反射方法操作空间,方便地调用和修改其中的方法和变量 +- 修复了 Hook 过程中的大量 BUG,感谢 [WankkoRee](https://github.com/WankkoRee) 对此项目所做出的贡献 + +### 1.0.69 | 2022.03.30 + +- 添加并改进一些方法功能的注释 +- 增加 Demo 中的更多示例 Hook 内容 +- 修复在一个 Hook 实例中,`allMethods` 多次使用时只有最后一个生效的问题,感谢 [WankkoRee](https://github.com/WankkoRee) 的反馈 + +### 1.0.68 | 2022.03.29 + +- 增加 Demo 中的新用例和 LSPosed 作用域 +- 增加 `Member` 查找缓存和查找缓存配置开关 +- 移除和修改 `MethodFinder`、`FieldFinder` 以及 `HookParam` 相关方法的调用 +- 增加更多 `Finder` 中的 `cast` 类型并支持 `cast` 为数组 +- 整体的性能和稳定性提升 +- 修复上一个版本可能存在的 BUG + +### 1.0.67 | 2022.03.27 + +- 增加三个 `Finder` 中的 `modifiers` 功能,可筛选 `static`、`native`、`public`、`abstract` 等诸多描述类型 +- 增加方法和构造方法查找时可模糊方法参数类型为指定个数进行查找 +- 增加 `Member` 的 `hasModifiers` 扩展功能 +- 增加 `MethodFinder` 和 `ConstructorFinder` 中的 `give` 方法,可获得原始类型 +- 增加 `YukiHookModulePrefs` 中的 `PrefsData` 模板功能 +- 彻底对方法、构造方法及变量的查找方案进行重构 +- 优化代码注释,修复了可能产生的 BUG + +### 1.0.66 | 2022.03.25 + +- 修复 `MethodFinder` 中的一个严重问题 +- 增加 `hookParam` 中的 `args` 调用方法 +- 修复其它可能存在的问题以及修复部分类的注释问题 + +### 1.0.65 | 2022.03.25 + +- 重新发布版本修复 Maven 仓库因为缓存问题新版本不正确的情况 +- 增加 `MethodFinder` 与 `FieldFinder` 新的返回值调用方法 +- 修复可能存在的问题,并修复太极使用过程中可能存在的问题 +- 修复自动生成 Xposed 入口类可能发生的问题 +- 增加了 `type` 中的 `android` 类型以及 `java` 类型 + +### 1.0.6 | 2022.03.20 + +- 修复 `YukiHookModulePrefs` 在使用一次 `direct` 忽略缓存后每次都忽略的 BUG +- 增加新的 API,作废了 `isActive` 判断模块激活的传统用法 +- 修复非 Xposed 环境使用 API 时打印调试日志的问题 +- 修复查找 `Field` 时的日志输出问题和未拦截的异常问题 +- 解耦合 `ReflectionUtils` 中的 Xposed API +- 增加 `YukiHookModuleStatus` 方法名称的混淆,以精简模块生成的体积 +- 装载模块自身 Hook 时将不再打印欢迎信息 +- 修复上一个版本仍然存在的某些 BUG + +### 1.0.55 | 2022.03.18 + +- 修正一处注释错误 +- 临时修复一个 BUG +- 增加了 `type` 中的大量 `android` 类型以及少量 `java` 类型 +- 修复新版与旧版 Kotlin APIs 的兼容性问题 + +### 1.0.5 | 2022.03.18 + +- 修复旧版本 LSPosed 框架情况下欢迎信息多次打印的问题 +- 添加 `onInit` 方法来配置 `YukiHookAPI` +- 新增 `executorName` 和 `executorVersion` 来获取当前 Hook 框架的名称和版本号 +- 新增 `by` 方法来设置 Hook 的时机和条件 +- `YukiHookModulePrefs` 新增可控制的键值缓存,可在宿主运行时模块动态更新数据 +- 修复了一些可能存在的 BUG + +### 1.0.4 | 2022.03.06 + +- 修复 LSPosed 在最新版本中启用“只有模块classloader可以使用Xposed API”选项后找不到 `XposedBridge` 的问题 +- 添加 `YukiHookAPI` 的常量版本名称和版本号 +- 新增 `hasField` 方法以及 `isAllowPrintingLogs` 配置参数 +- 新增 `isDebug` 开启的情况下 API 将自动打印欢迎信息测试模块是否生效 + +### 1.0.3 | 2022.03.02 + +- 修复一个潜在性的异常未拦截 BUG +- 增加 `ignoredError` 功能 +- 增加了 `type` 中的 `android` 类型 +- 增加监听 `hook` 后的 `ClassNotFound` 功能 + +### 1.0.2 | 2022.02.18 + +- 修复 Windows 下无法找到项目路径的问题 +- 移除部分反射 API,合并至 `BaseFinder` 进行整合 +- 增加直接使用字符串创建 Hook 的方法 + +### 1.0.1 | 2022.02.15 + +- `RemedyPlan` 增加 `onFind` 功能 +- 整合并修改了部分反射 API 代码 +- 增加了 `type` 中的 `java` 类型 +- 修复忽略错误在控制台仍然输出的问题 + +### 1.0 | 2022.02.14 + +- 首个版本提交至 Maven \ No newline at end of file diff --git a/docs/about/contacts.md b/docs/about/contacts.md new file mode 100644 index 00000000..73bc4424 --- /dev/null +++ b/docs/about/contacts.md @@ -0,0 +1,11 @@ +# 联系我们 + +> 如在使用中有任何问题,或有任何建设性的建议,都可以联系我们。 + +加入我们 [点击加入 Telegram 群组](https://t.me/YukiHookAPI) + +在酷安找到我 [@星夜不荟](http://www.coolapk.com/u/876977) + +## 助力维护 + +感谢您选择并使用 `YukiHookAPI`,如有代码相关的建议和请求,可在 Github 提交 Pull Request。 \ No newline at end of file diff --git a/docs/about/future.md b/docs/about/future.md new file mode 100644 index 00000000..6ea1113c --- /dev/null +++ b/docs/about/future.md @@ -0,0 +1,35 @@ +# 展望未来 + +> 未来是美好的,也是不确定的,让我们共同期待 `YukiHookAPI` 在未来的发展空间。 + +## 未解决的问题 + +> 这里收录了 `YukiHookAPI` 尚未解决的问题。 + +### YukiHookModulePrefs + +目前仅限完美支持 LSPosed,其它 Xposed 框架需要降级模块 API。 + +可能完全不支持太极,太极在高版本系统上需要更低的 API 才能适配。 + +部分 Xposed 模块开发者目前选择 Hook 目标 APP 内置 Sp 存储方案解决模块设置共享问题。 + +后期 Android 系统的权限将越来越严格,`selinux` 就是目前面临的一个大问题,有待讨论和研究。 + +## 未来的计划 + +> 这里收录了 `YukiHookAPI` 可能会在后期添加的功能。 + +### 支持资源 Hook 和注入系统框架 + +目前的 API 仅支持 APP 内的功能 Hook,并不支持 `Resource` 的替换以及 Hook 系统框架。 + +API 还未实现对 `handleInitPackageResources` 和 `initZygote` 的调用。 + +在未来会根据使用和需求人数加上这个功能,如有需求你也可以向我们提交 Pull Request 来贡献你的代码。 + +### 支持更多 Hook Framework + +作为 API 来讲,目前仅仅对接 `XposedBridge` 作为兼容层,还是有一定的局限性。 + +大部分 `inline hook` 没有 `Java` 兼容层,后期可能会考虑 `native hook` 的 `Java` 兼容层适配。 \ No newline at end of file diff --git a/docs/api/document.md b/docs/api/document.md new file mode 100644 index 00000000..5b40cb69 --- /dev/null +++ b/docs/api/document.md @@ -0,0 +1,53 @@ +# Public API + +> 以下内容为公开的 API 接口,在版本变更时将同步至最新。 + +[filename](public/YukiHookAPI.md ':include') + +[filename](public/PackageParam.md ':include') + +[filename](public/HookParam.md ':include') + +[filename](public/YukiHookModuleStatus.md ':include') + +[filename](public/InjectYukiHookWithXposed.md ':include') + +[filename](public/YukiHookXposedInitProxy.md ':include') + +[filename](public/YukiHookModulePrefs.md ':include') + +[filename](public/PrefsData.md ':include') + +[filename](public/ComponentTypeFactory.md ':include') + +[filename](public/GraphicsTypeFactory.md ':include') + +[filename](public/ViewTypeFactory.md ':include') + +[filename](public/VariableTypeFactory.md ':include') + +[filename](public/LoggerFactory.md ':include') + +[filename](public/ReflectionFactory.md ':include') + +[filename](public/YukiHookFactory.md ':include') + +[filename](public/YukiBaseHooker.md ':include') + +[filename](public/YukiHookCreater.md ':include') + +[filename](public/MethodFinder.md ':include') + +[filename](public/ConstructorFinder.md ':include') + +[filename](public/FieldFinder.md ':include') + +[filename](public/BaseFinder-IndexTypeCondition.md ':include') + +[filename](public/ModifierRules.md ':include') + +[filename](public/HookClass.md ':include') + +[filename](public/VariousClass.md ':include') + +[filename](public/CurrentClass.md ':include') \ No newline at end of file diff --git a/docs/api/home.md b/docs/api/home.md new file mode 100644 index 00000000..ff7616a5 --- /dev/null +++ b/docs/api/home.md @@ -0,0 +1,27 @@ +# 文档介绍 + +> 这里的文档将同步最新 API 版本的相关用法,请保持 `YukiHookAPI` 为最新版本以使用最新版本的功能。 + +## 功能描述说明 + +> 功能描述主要介绍当前 API 的相关用法和用途。 + +## 功能示例说明 + +> 功能示例主要展示了当前 API 的基本用法示例,可供参考。 + +## 变更记录说明 + +首个版本的功能将标记为 `v$version` `添加`; + +后期新增加的功能将标记为 `v$version` `新增`; + +后期修改的功能将被追加为 `v$version` `修改`; + +后期被作废的功能将标记为 `v$version` `作废` 并会标注删除线; + +后期被删除的功能将标记为 `v$version` `移除` 并会标注删除线。 + +## 正文 + +> 你可以从侧边栏找到 `Public API` 或 [点击这里](api/document) 进入 API 文档。 \ No newline at end of file diff --git a/docs/api/public/BaseFinder-IndexTypeCondition.md b/docs/api/public/BaseFinder-IndexTypeCondition.md new file mode 100644 index 00000000..2a46ccc9 --- /dev/null +++ b/docs/api/public/BaseFinder-IndexTypeCondition.md @@ -0,0 +1,101 @@ +## BaseFinder.IndexTypeCondition [class] + +```kotlin +inner class IndexTypeCondition(private val type: IndexConfigType) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 字节码下标筛选实现类。 + +### index [method] + +```kotlin +fun index(num: Int) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 设置下标。 + +若 `index` 小于零则为倒序,此时可以使用 `IndexTypeConditionSort.reverse` 方法实现。 + +可使用 `IndexTypeConditionSort.first` 和 `IndexTypeConditionSort.last` 设置首位和末位筛选条件。 + +### index [method] + +```kotlin +fun index(): IndexTypeConditionSort +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 得到下标。 + +### IndexTypeConditionSort [class] + +```kotlin +inner class IndexTypeConditionSort +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 字节码下标排序实现类。 + +#### first [method] + +```kotlin +fun first() +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 设置满足条件的第一个。 + +#### last [method] + +```kotlin +fun last() +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 设置满足条件的最后一个。 + +#### reverse [method] + +```kotlin +fun reverse(num: Int) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 设置倒序下标。 \ No newline at end of file diff --git a/docs/api/public/ComponentTypeFactory.md b/docs/api/public/ComponentTypeFactory.md new file mode 100644 index 00000000..2f4129be --- /dev/null +++ b/docs/api/public/ComponentTypeFactory.md @@ -0,0 +1,11 @@ +## ComponentTypeFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是一个预置 Hook 类型的常量类,主要为 `Android` 相关组件的 `Class` 内容,跟随版本更新会逐一进行增加。 + +详情可 [点击这里](https://github.com/fankes/YukiHookAPI/blob/master/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/type/android/ComponentTypeFactory.kt) 进行查看。 \ No newline at end of file diff --git a/docs/api/public/ConstructorFinder.md b/docs/api/public/ConstructorFinder.md new file mode 100644 index 00000000..ec8845c6 --- /dev/null +++ b/docs/api/public/ConstructorFinder.md @@ -0,0 +1,380 @@ +## ConstructorFinder [class] + +```kotlin +class ConstructorFinder(override val hookInstance: YukiHookCreater.MemberHookCreater?, override val classSet: Class<*>) : BaseFinder() +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.2` `修改` + +合并到 `BaseFinder` + +功能描述 + +> `Constructor` 查找类。 + +### paramCount [field] + +```kotlin +var paramCount: Int +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> `Constructor` 参数个数。 + +你可以不使用 `param` 指定参数类型而是仅使用此变量指定参数个数。 + +若参数个数小于零则忽略并使用 `param`。 + +### modifiers [method] + +```kotlin +fun modifiers(initiate: ModifierRules.() -> Unit): IndexTypeCondition +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> `Constructor` 筛选条件。 + +可不设置筛选条件,默认模糊查找并取第一个匹配的 `Constructor`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### param [method] + +```kotlin +fun param(vararg paramType: Any): IndexTypeCondition +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Constructor` 参数。 + +如果同时使用了 `paramCount` 则 `paramTypes` 的数量必须与 `paramCount` 完全匹配。 + +!> 无参 `Constructor` 不要使用此方法。 + +!> 有参 `Constructor` 必须使用此方法设定参数或使用 `paramCount` 指定个数。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### RemedyPlan [class] + +```kotlin +inner class RemedyPlan +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Constructor` 重查找实现类,可累计失败次数直到查找成功。 + +#### constructor [method] + +```kotlin +fun constructor(initiate: ConstructorFinder.() -> Unit) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建需要重新查找的 `Constructor`。 + +你可以添加多个备选构造方法,直到成功为止,若最后依然失败,将停止查找并输出错误日志。 + +#### Result [class] + +```kotlin +inner class Result +``` + +变更记录 + +`v1.0.1` `新增` + +功能描述 + +> `RemedyPlan` 结果实现类。 + +##### onFind [method] + +```kotlin +fun onFind(initiate: Constructor<*>.() -> Unit) +``` + +变更记录 + +`v1.0.1` `新增` + +功能描述 + +> 当在 `RemedyPlan` 中找到结果时。 + +功能示例 + +你可以方便地对重查找的 `Constructor` 实现 `onFind` 方法。 + +> 示例如下 + +```kotlin +constructor { + // Your code here. +}.onFind { + // Your code here. +} +``` + +### Result [class] + +```kotlin +inner class Result(internal val isNoSuch: Boolean, private val e: Throwable?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Constructor` 查找结果实现类。 + +#### result [method] + +```kotlin +fun result(initiate: Result.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建监听结果事件方法体。 + +功能示例 + +你可以使用 `lambda` 形式创建 `Result` 类。 + +> 示例如下 + +```kotlin +constructor { + // Your code here. +}.result { + get().call() + remedys {} + onNoSuchConstructor {} +} +``` + +#### get [method] + +```kotlin +fun get(): Instance +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 获得 `Constructor` 实例处理类。 + +!> 若你设置了 `remedys` 请使用 `wait` 回调结果方法。 + +功能示例 + +你可以通过获得方法所在实例来执行构造方法创建新的实例对象。 + +> 示例如下 + +```kotlin +constructor { + // Your code here. +}.get().call() +``` + +你可以 `cast` 构造方法为指定类型的实例对象。 + +> 示例如下 + +```kotlin +constructor { + // Your code here. +}.get().newInstance() +``` + +!> 若构造方法含有参数则后方参数必填。 + +> 示例如下 + +```kotlin +constructor { + // Your code here. +}.get().newInstance("param1", "param2") +``` + +#### give [method] + +```kotlin +fun give(): Constructor<*>? +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 得到构造方法本身。 + +#### wait [method] + +```kotlin +fun wait(initiate: Instance.() -> Unit) +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 获得 `Constructor` 实例处理类,配合 `RemedyPlan` 使用。 + +!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 + +!> 若你没有设置 `remedys` 此方法将不会被回调。 + +#### remedys [method] + +```kotlin +fun remedys(initiate: RemedyPlan.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建构造方法重查找功能。 + +功能示例 + +当你遇到一种构造方法可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchConstructor` 捕获异常二次查找构造方法。 + +若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 + +> 示例如下 + +```kotlin +constructor { + // Your code here. +}.remedys { + constructor { + // Your code here. + } + constructor { + // Your code here. + } +} +``` + +#### onNoSuchConstructor [method] + +```kotlin +fun onNoSuchConstructor(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听找不到构造方法时。 + +只会返回第一次的错误信息,不会返回 `RemedyPlan` 的错误信息。 + +#### ignoredError [method] + +```kotlin +fun ignoredError(): Result +``` + +变更记录 + +`v1.0.3` `新增` + +功能描述 + +> 忽略任何错误发出的警告。 + +若 `isNotIgnoredHookingFailure` 为 `false` 则自动忽略。 + +#### Instance [class] + +```kotlin +inner class Instance +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> `Constructor` 实例处理类。 + +##### call [method] + +```kotlin +fun call(vararg param: Any?): Any? +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 执行构造方法创建目标实例,不指定目标实例类型。 + +##### newInstance [method] + +```kotlin +fun newInstance(vararg param: Any?): T? +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 执行构造方法创建目标实例 ,指定 `T` 目标实例类型。 \ No newline at end of file diff --git a/docs/api/public/CurrentClass.md b/docs/api/public/CurrentClass.md new file mode 100644 index 00000000..89d3744c --- /dev/null +++ b/docs/api/public/CurrentClass.md @@ -0,0 +1,41 @@ +## CurrentClass [class] + +```kotlin +class CurrentClass(private val instance: Class<*>, private val self: Any) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 当前实例的类操作对象。 + +### field [method] + +```kotlin +fun field(initiate: FieldFinder.() -> Unit): FieldFinder.Result.Instance +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 调用当前实例中的变量。 + +### method [method] + +```kotlin +fun method(initiate: MethodFinder.() -> Unit): MethodFinder.Result.Instance +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 调用当前实例中的方法。 \ No newline at end of file diff --git a/docs/api/public/FieldFinder.md b/docs/api/public/FieldFinder.md new file mode 100644 index 00000000..684108ab --- /dev/null +++ b/docs/api/public/FieldFinder.md @@ -0,0 +1,560 @@ +## FieldFinder [class] + +```kotlin +class FieldFinder(override val hookInstance: YukiHookCreater.MemberHookCreater?, override val classSet: Class<*>?) : BaseFinder() +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.2` `修改` + +合并到 `BaseFinder` + +功能描述 + +> `Field` 查找类。 + +### ~~classSet [field]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.2` `移除` + +### name [field] + +```kotlin +var name: String +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.70` `修改` + +允许不填写名称 + +功能描述 + +> `Field` 名称。 + +!> 若不填写名称则必须存在一个其它条件。 + +### type [field] + +```kotlin +var type: Any? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Field` 类型。 + +可不填写类型,默认模糊查找并取第一个匹配的 `Field`。 + +### modifiers [method] + +```kotlin +fun modifiers(initiate: ModifierRules.() -> Unit): IndexTypeCondition +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> `Field` 筛选条件。 + +可不设置筛选条件,默认模糊查找并取第一个匹配的 `Field`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### order [method] + +```kotlin +fun order(): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 顺序筛选字节码的下标。 + +### name [method] + +```kotlin +fun name(value: String): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> `Field` 名称。 + +!> 若不填写名称则必须存在一个其它条件,默认模糊查找并取第一个匹配的 `Field`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### type [method] + +```kotlin +fun type(value: Any): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> `Field` 类型。 + +!> 可不填写类型,默认模糊查找并取第一个匹配的 `Field`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### Result [class] + +```kotlin +inner class Result(internal val isNoSuch: Boolean, private val e: Throwable?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Field` 查找结果实现类。 + +#### result [method] + +```kotlin +fun result(initiate: Result.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建监听结果事件方法体。 + +功能示例 + +你可以使用 `lambda` 形式创建 `Result` 类。 + +> 示例如下 + +```kotlin +field { + // Your code here. +}.result { + get(instance).set("something") + get(instance).string() + get(instance).cast() + get().boolean() + give() + onNoSuchField {} +} +``` + +#### get [method] +```kotlin +fun get(instance: Any?): Instance +``` +变更记录 + +`v1.0` `添加` + +功能描述 + +> 得到变量实例处理类。 + +功能示例 + +你可以轻松地得到 `Field` 的实例以及使用它进行设置实例。 + +> 示例如下 + +```kotlin +field { + // Your code here. +}.get(instance).set("something") +``` + +如果你取到的是静态 `Field`,可以不需要设置实例。 + +> 示例如下 + +```kotlin +field { + // Your code here. +}.get().set("something") +``` + +#### give [method] + +```kotlin +fun give(): Field? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 得到变量本身。 + +#### onNoSuchField [method] + +```kotlin +fun onNoSuchField(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听找不到变量时。 + +#### ignoredError [method] + +```kotlin +fun ignoredError(): Result +``` + +变更记录 + +`v1.0.3` `新增` + +功能描述 + +> 忽略任何错误发出的警告。 + +若 `isNotIgnoredHookingFailure` 为 `false` 则自动忽略。 + +#### Instance [class] + +```kotlin +inner class Instance(private val instance: Any?, val self: Any?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Field` 实例变量处理类。 + +##### cast [method] + +```kotlin +fun cast(): T? +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.68` `修改` + +修改 ~~`of`~~ 为 `cast` + +移动方法到 `Instance` + +功能描述 + +> 得到变量实例。 + +##### byte [method] + +```kotlin +fun byte(): Byte? +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到变量 Byte 实例。 + +##### int [method] + +```kotlin +fun int(): Int +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofInt`~~ 为 `int` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Int 实例。 + +##### long [method] + +```kotlin +fun long(): Long +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofLong`~~ 为 `long` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Long 实例。 + +##### short [method] + +```kotlin +fun short(): Short +``` +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofShort`~~ 为 `short` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Short 实例。 + +##### double [method] + +```kotlin +fun double(): Double +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofDouble`~~ 为 `double` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Double 实例。 + +##### float [method] + +```kotlin +fun float(): Float +``` +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofFloat`~~ 为 `float` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Float 实例。 + +##### string [method] + +```kotlin +fun string(): String +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofString`~~ 为 `string` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 String 实例。 + +##### char [method] + +```kotlin +fun char(): Char +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到变量 Char 实例。 + +##### boolean [method] + +```kotlin +fun boolean(): Boolean +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofBoolean`~~ 为 `boolean` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Boolean 实例。 + +##### any [method] + +```kotlin +fun any(): Any? +``` +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofAny`~~ 为 `any` + +移动方法到 `Instance` + +功能描述 + +> 得到变量 Any 实例。 + +##### array [method] + +```kotlin +inline fun array(): Array +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到变量 Array 实例。 + +##### list [method] + +```kotlin +inline fun list(): List +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到变量 List 实例。 + +##### set [method] + +```kotlin +fun set(any: Any?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置变量实例。 + +##### setTrue [method] + +```kotlin +fun setTrue() +``` +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置变量实例为 `true`。 + +!> 请确保实例对象类型为 `Boolean`。 + +##### setFalse [method] + +```kotlin +fun setFalse() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置变量实例为 `false`。 + +!> 请确保实例对象类型为 `Boolean`。 + +##### setNull [method] + +```kotlin +fun setNull() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置变量实例为 `null`。 \ No newline at end of file diff --git a/docs/api/public/GraphicsTypeFactory.md b/docs/api/public/GraphicsTypeFactory.md new file mode 100644 index 00000000..64463c72 --- /dev/null +++ b/docs/api/public/GraphicsTypeFactory.md @@ -0,0 +1,11 @@ +## GraphicsTypeFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是一个预置 Hook 类型的常量类,主要为 `Android` 相关 `Graphics` 的 `Class` 内容,跟随版本更新会逐一进行增加。 + +详情可 [点击这里](https://github.com/fankes/YukiHookAPI/blob/master/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/type/android/GraphicsTypeFactory.kt) 进行查看。 \ No newline at end of file diff --git a/docs/api/public/HookClass.md b/docs/api/public/HookClass.md new file mode 100644 index 00000000..6f53c23f --- /dev/null +++ b/docs/api/public/HookClass.md @@ -0,0 +1,15 @@ +## HookClass [class] + +```kotlin +class HookClass(var instance: Class<*>?, var name: String, var throwable: Throwable?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建一个当前 Hook 的 `Class` 接管类。 + +`instance` 为实例,`name` 为实例完整包名,`throwable` 为找不到实例的时候抛出的异常。 \ No newline at end of file diff --git a/docs/api/public/HookParam.md b/docs/api/public/HookParam.md new file mode 100644 index 00000000..18f38dd2 --- /dev/null +++ b/docs/api/public/HookParam.md @@ -0,0 +1,580 @@ +## HookParam [class] + +```kotlin +class HookParam(private​ ​val​ ​createrInstance​:​ ​YukiHookCreater​, private val wrapper: HookParamWrapper) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> Hook 方法、构造类的目标对象实现类。 + +### args [field] + +```kotlin +val args: Array +``` + +变更记录 + +在 `v1.0` 添加 + +功能描述 + +> 获取当前 Hook 对象 `member` 或 `constructor` 的参数对象数组。 + +### firstArgs [field] + +```kotlin +val firstArgs: Any? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 对象 `member` 或 `constructor` 的参数对象数组第一位。 + +### lastArgs [field] + +```kotlin +val lastArgs: Any? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 对象 `member` 或 `constructor` 的参数对象数组最后一位。 + +### instance [field] + +```kotlin +val instance: Any +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 实例的对象。 + +!> 如果你当前 Hook 的对象是一个静态,那么它将不存在实例的对象。 + +### instanceClass [field] + +```kotlin +val instanceClass: Class<*> +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 实例的类对象。 + +### method [field] + +```kotlin +val method: Method +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 对象的方法。 + +### constructor [field] + +```kotlin +val constructor: Constructor +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 对象的构造方法。 + +### result [field] + +```kotlin +var result: Any? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取、设置当前 Hook 对象的 `method` 或 `constructor` 的返回值。 + +### firstArgs [method] + +```kotlin +inline fun firstArgs(): T? +``` + +变更记录 + +`v1.0.66` `新增` + +功能描述 + +> 获取当前 Hook 对象 [method] or [constructor] 的参数对象数组第一位 `T`。 + +### lastArgs [method] + +```kotlin +inline fun lastArgs(): T? +``` + +变更记录 + +`v1.0.66` `新增` + +功能描述 + +> 获取当前 Hook 对象 [method] or [constructor] 的参数对象数组最后一位 `T`。 + +### instance [method] + +```kotlin +inline fun instance(): T +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 实例的对象 `T`。 + +功能示例 + +你可以通过 `instance` 方法轻松使用泛型 `cast` 为目标对象的类型。 + +> 示例如下 + +```kotlin +instance().finish() +``` + +### args [method] + +```kotlin +fun args(index: Int): ArgsModifyer +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook 对象的 `method` 或 `constructor` 的参数实例化对象类。 + +功能示例 + +你可以通过 `args` 方法修改当前 Hook 实例的方法、构造方法的参数内容。 + +`index` 的默认值为 `0`,如果只有一位 `param` 参数,你可以不写。 + +你可以直接使用 `set` 方法设置 `param` 为你的目标实例,接受 `Any` 类型。 + +!> 请确保 `param` 类型为你的目标实例类型。 + +> 示例如下 + +```kotlin +args().set("modify the value") +``` + +你还可以使用 `setNull` 方法设置 `param` 为空。 + +> 示例如下 + +```kotlin +args(index = 1).setNull() +``` + +你还可以使用 `setTrue` 方法设置 `param` 为 `true`。 + +!> 请确保 `param` 类型为 `Boolean`。 + +> 示例如下 + +```kotlin +args(index = 2).setTrue() +``` + +你还可以使用 `setFalse` 方法设置 `param` 为 `false`。 + +!> 请确保 `param` 类型为 `Boolean`。 + +> 示例如下 + +```kotlin +args(index = 3).setFalse() +``` + +### invokeOriginal [method] + +```kotlin +fun Member.invokeOriginal(vararg args: Any?): Any? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 执行原始 `Member`。 + +功能实例 + +此方法可以 `invoke` 原始未经 Hook 的 `Member` 对象,取决于原始 `Member` 的参数和类型。 + +> 示例如下 + +```kotlin +member.invokeOriginal("test value") +``` + +### resultTrue [method] + +```kotlin +fun resultTrue() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置当前 Hook 对象方法的 `result` 返回值为 `true`。 + +!> 请确保 `result` 类型为 `Boolean`。 + +### resultFalse [method] + +```kotlin +fun resultFalse() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置当前 Hook 对象方法的 `result` 返回值为 `false`。 + +!> 请确保 `result` 类型为 `Boolean`。 + +### resultNull [method] + +```kotlin +fun resultNull() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +!> 此方法将强制设置 Hook 对象方法的 `result` 为 `null`。 + +### ArgsModifyer [class] + +```kotlin +inner class ArgsModifyer(private val index: Int) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 对方法参数的修改进行实例化类。 + +#### cast [method] + +```kotlin +fun cast(): T? +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`of`~~ 为 `cast` + +功能描述 + +> 得到方法参数的实例对象 `T`。 + +#### byte [method] + +```kotlin +fun byte(): Byte? +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到方法参数的实例对象 Byte。 + +#### int [method] + +```kotlin +fun int(): Int +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofInt`~~ 为 `int` + +功能描述 + +> 得到方法参数的实例对象 Int。 + +#### long [method] + +```kotlin +fun long(): Long +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofLong`~~ 为 `long` + +功能描述 + +> 得到方法参数的实例对象 Long。 + +#### short [method] + +```kotlin +fun short(): Short +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofShort`~~ 为 `short` + +功能描述 + +> 得到方法参数的实例对象 Short。 + +#### double [method] + +```kotlin +fun double(): Double +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofDouble`~~ 为 `double` + +功能描述 + +> 得到方法参数的实例对象 Double。 + +#### float [method] + +```kotlin +fun float(): Float +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofFloat`~~ 为 `float` + +功能描述 + +> 得到方法参数的实例对象 Float。 + +#### string [method] + +```kotlin +fun string(): String +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofString`~~ 为 `string` + +功能描述 + +> 得到方法参数的实例对象 String。 + +#### char [method] + +```kotlin +fun char(): Char +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到方法参数的实例对象 Char。 + +#### boolean [method] + +```kotlin +fun boolean(): Boolean +``` + +变更记录 + +`v1.0.66` `新增` + +`v1.0.68` `修改` + +修改 ~~`ofBoolean`~~ 为 `boolean` + +功能描述 + +> 得到方法参数的实例对象 Boolean。 + +#### array [method] + +```kotlin +inline fun array(): Array +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到方法参数的实例对象 Array。 + +#### list [method] +```kotlin +inline fun list(): List +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 得到方法参数的实例对象 List。 + +#### set [method] + +```kotlin +fun set(any: T?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置方法参数的实例对象。 + +#### setNull [method] + +```kotlin +fun setNull() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置方法参数的实例对象为 `null`。 + +#### setTrue [method] + +```kotlin +fun setTrue() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置方法参数的实例对象为 `true`。 + +!> 请确保目标对象的类型是 `Boolean`。 + +#### setFalse [method] + +```kotlin +fun setFalse() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 设置方法参数的实例对象为 `false`。 + +!> 请确保目标对象的类型是 `Boolean`。 \ No newline at end of file diff --git a/docs/api/public/InjectYukiHookWithXposed.md b/docs/api/public/InjectYukiHookWithXposed.md new file mode 100644 index 00000000..a94bb2be --- /dev/null +++ b/docs/api/public/InjectYukiHookWithXposed.md @@ -0,0 +1,17 @@ +## InjectYukiHookWithXposed [annotation] + +```kotlin +annotation class InjectYukiHookWithXposed(val sourcePath: String, val modulePackageName: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 标识 `YukiHookAPI` 注入 Xposed 入口的类注释。 + +功能示例 + +详情请参考 [InjectYukiHookWithXposed 注释](config/xposed-using?id=injectyukihookwithxposed-注释)。 \ No newline at end of file diff --git a/docs/api/public/LoggerFactory.md b/docs/api/public/LoggerFactory.md new file mode 100644 index 00000000..3c64fb07 --- /dev/null +++ b/docs/api/public/LoggerFactory.md @@ -0,0 +1,73 @@ +## LoggerFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是 `YukiHookAPI` 的日志封装类,可实现同时向 `Logcat` 和 `XposedBridge.log` 打印日志的功能。 + +### loggerD [method] + +```kotlin +fun loggerD(tag: String, msg: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `D`。 + +`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 + +### loggerI [method] + +```kotlin +fun loggerI(tag: String, msg: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `I`。 + +`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 + +### loggerW [method] + +```kotlin +fun loggerW(tag: String, msg: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `W`。 + +`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 + +### loggerE [method] + +```kotlin +fun loggerE(tag: String, msg: String, e: Throwable?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `E`,可携带 `e` 异常信息,将打印异常堆栈。 + +`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 \ No newline at end of file diff --git a/docs/api/public/MethodFinder.md b/docs/api/public/MethodFinder.md new file mode 100644 index 00000000..7972767f --- /dev/null +++ b/docs/api/public/MethodFinder.md @@ -0,0 +1,656 @@ +## MethodFinder [class] + +```kotlin +class MethodFinder(override val hookInstance: YukiHookCreater.MemberHookCreater?, override val classSet: Class<*>) : BaseFinder() +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.2` `修改` + +合并到 `BaseFinder` + +功能描述 + +> `Method` 查找类。 + +### name [field] + +```kotlin +var name: String +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.70` `修改` + +允许不填写名称 + +功能描述 + +> `Method` 名称。 + +!> 若不填写名称则必须存在一个其它条件。 + +### paramCount [field] + +```kotlin +var paramCount: Int +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> `Method` 参数个数。 + +你可以不使用 `param` 指定参数类型而是仅使用此变量指定参数个数。 + +若参数个数小于零则忽略并使用 `param`。 + +### returnType [field] + +```kotlin +var returnType: Any? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Method` 返回值,可不填写返回值,默认模糊查找并取第一个匹配的 `Method`。 + +### modifiers [method] + +```kotlin +fun modifiers(initiate: ModifierRules.() -> Unit): IndexTypeCondition +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> `Method` 筛选条件。 + +可不设置筛选条件,默认模糊查找并取第一个匹配的 `Method`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### param [method] + +```kotlin +fun param(vararg paramType: Any): IndexTypeCondition +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Method` 参数。 + +如果同时使用了 `paramCount` 则 `paramTypes` 的数量必须与 `paramCount` 完全匹配。 + +!> 无参 `Method` 不要使用此方法。 + +!> 有参 `Method` 必须使用此方法设定参数或使用 `paramCount` 指定个数。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### order [method] + +```kotlin +fun order(): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 顺序筛选字节码的下标。 + +### name [method] + +```kotlin +fun name(value: String): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> `Method` 名称。 + +!> 若不填写名称则必须存在一个其它条件,默认模糊查找并取第一个匹配的 `Method`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### paramCount [method] + +```kotlin +fun paramCount(num: Int): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> `Method` 参数个数。 + +你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数。 + +若参数个数小于零则忽略并使用 `param`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### returnType [method] + +```kotlin +fun returnType(value: Any): IndexTypeCondition +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> `Method` 返回值。 + +可不填写返回值,默认模糊查找并取第一个匹配的 `Method`。 + +!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 + +### RemedyPlan [class] + +```kotlin +inner class RemedyPlan +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Method` 重查找实现类,可累计失败次数直到查找成功。 + +#### method [method] + +```kotlin +fun method(initiate: MethodFinder.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建需要重新查找的 `Method`。 + +你可以添加多个备选方法,直到成功为止,若最后依然失败,将停止查找并输出错误日志。 + +#### Result [class] + +```kotlin +inner class Result +``` + +变更记录 + +`v1.0.1` `新增` + +功能描述 + +> `RemedyPlan` 结果实现类。 + +##### onFind [method] + +```kotlin +fun onFind(initiate: Method.() -> Unit) +``` + +变更记录 + +`v1.0.1` `新增` + +功能描述 + +> 当在 `RemedyPlan` 中找到结果时。 + +功能示例 + +你可以方便地对重查找的 `Method` 实现 `onFind` 方法。 + +> 示例如下 + +```kotlin +method { + // Your code here. +}.onFind { + // Your code here. +} +``` + +### Result [class] + +```kotlin +inner class Result(internal val isNoSuch: Boolean, private val e: Throwable?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `Method` 查找结果实现类。 + +#### result [method] + +```kotlin +fun result(initiate: Result.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建监听结果事件方法体。 + +功能示例 + +你可以使用 `lambda` 形式创建 `Result` 类。 + +> 示例如下 + +```kotlin +method { + // Your code here. +}.result { + get(instance).call() + remedys {} + onNoSuchMethod {} +} +``` + +#### get [method] + +```kotlin +fun get(instance: Any?): Instance +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 获得 `Method` 实例处理类。 + +!> 若你设置了 `remedys` 请使用 `wait` 回调结果方法。 + +功能示例 + +你可以通过获得方法所在实例来执行方法。 + +> 示例如下 + +```kotlin +method { + // Your code here. +}.get(instance).call() +``` + +若当前为静态方法,你可以不设置实例。 + +> 示例如下 + +```kotlin +method { + // Your code here. +}.get().call() +``` + +#### give [method] + +```kotlin +fun give(): Method? +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 得到方法本身。 + +#### wait [method] + +```kotlin +fun wait(instance: Any?, initiate: Instance.() -> Unit) +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 获得 `Method` 实例处理类,配合 `RemedyPlan` 使用。 + +!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 + +!> 若你没有设置 `remedys` 此方法将不会被回调。 + +#### remedys [method] + +```kotlin +fun remedys(initiate: RemedyPlan.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 创建方法重查找功能。 + +功能示例 + +当你遇到一种方法可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchMethod` 捕获异常二次查找方法。 + +若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 + +> 示例如下 + +```kotlin +method { + // Your code here. +}.remedys { + method { + // Your code here. + } + method { + // Your code here. + } +} +``` + +#### onNoSuchMethod [method] + +```kotlin +fun onNoSuchMethod(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听找不到方法时。 + +只会返回第一次的错误信息,不会返回 `RemedyPlan` 的错误信息。 + +#### ignoredError [method] + +```kotlin +fun ignoredError(): Result +``` + +变更记录 + +`v1.0.3` `新增` + +功能描述 + +> 忽略任何错误发出的警告。 + +若 `isNotIgnoredHookingFailure` 为 `false` 则自动忽略。 + +#### Instance [class] + +```kotlin +inner class Instance(private val instance: Any?) +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> `Method` 实例处理类。 + +##### call [method] + +```kotlin +fun call(vararg param: Any?): Any? +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 执行方法,不指定返回值类型。 + +##### invoke [method] + +```kotlin +fun invoke(vararg param: Any?): T? +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 执行方法,指定 `T` 返回值类型。 + +##### byte [method] + +```kotlin +fun byte(vararg param: Any?): Byte? +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 执行方法,指定 Byte 返回值类型。 + +##### int [method] + +```kotlin +fun int(vararg param: Any?): Int +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callInt`~~ 为 `int` + +功能描述 + +> 执行方法,指定 Int 返回值类型。 + +##### long [method] + +```kotlin +fun long(vararg param: Any?): Long +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callLong`~~ 为 `long` + +功能描述 + +> 执行方法,指定 Long 返回值类型。 + +##### short [method] + +```kotlin +fun short(vararg param: Any?): Short +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callShort`~~ 为 `short` + +功能描述 + +> 执行方法,指定 Short 返回值类型。 + +##### double [method] + +```kotlin +fun double(vararg param: Any?): Double +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callDouble`~~ 为 `double` + +功能描述 + +> 执行方法,指定 Double 返回值类型。 + +##### float [method] + +```kotlin +fun float(vararg param: Any?): Float +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callFloat`~~ 为 `float` + +功能描述 + +> 执行方法,指定 Float 返回值类型。 + +##### string [method] + +```kotlin +fun string(vararg param: Any?): String +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callString`~~ 为 `string` + +功能描述 + +> 执行方法,指定 String 返回值类型。 + +##### char [method] + +```kotlin +fun char(vararg param: Any?): Char +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 执行方法,指定 Char 返回值类型。 + +##### boolean [method] + +```kotlin +fun boolean(vararg param: Any?): Boolean +``` + +变更记录 + +`v1.0.65` `新增` + +`v1.0.68` `修改` + +修改 ~~`callBoolean`~~ 为 `boolean` + +功能描述 + +> 执行方法,指定 Boolean 返回值类型。 + +#### array [method] + +```kotlin +inline fun array(vararg param: Any?): Array +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 执行方法,指定 Array 返回值类型。 + +#### list [method] + +```kotlin +inline fun list(vararg param: Any?): List +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 执行方法,指定 List 返回值类型。 \ No newline at end of file diff --git a/docs/api/public/ModifierRules.md b/docs/api/public/ModifierRules.md new file mode 100644 index 00000000..e9d31612 --- /dev/null +++ b/docs/api/public/ModifierRules.md @@ -0,0 +1,193 @@ +## ModifierRules [class] + +```kotlin +class ModifierRules +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 这是一个 `Member` 描述符定义类。 + +可对 R8 混淆后的 `Member` 进行更加详细的定位。 + +### asPublic + +```kotlin +fun asPublic() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `public`。 + +### asPrivate + +```kotlin +fun asPrivate() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `private`。 + +### asProtected + +```kotlin +fun asProtected() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `protected`。 + +### asStatic + +```kotlin +fun asStatic() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `static`。 + +对于任意的静态 `Member` 可添加此描述进行确定。 + +!> 特别注意 Kotlin -> Jvm 后的 `object` 类中的方法并不是静态的。 + +### asFinal + +```kotlin +fun asFinal() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `final`。 + +!> 特别注意在 Kotlin -> Jvm 后没有 `open` 标识的 `Member` 和没有任何关联的 `Member` 都将为 `final`。 + +### asSynchronized + +```kotlin +fun asSynchronized() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `synchronized`。 + +### asVolatile + +```kotlin +fun asVolatile() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `volatile`。 + +### asTransient + +```kotlin +fun asTransient() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `transient`。 + +### asNative + +```kotlin +fun asNative() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `native`。 + +对于任意 JNI 对接的 `Member` 可添加此描述进行确定。 + +### asInterface + +```kotlin +fun asInterface() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `interface`。 + +### asAbstract + +```kotlin +fun asAbstract() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `abstract`。 + +对于任意的抽象 `Member` 可添加此描述进行确定。 + +### asStrict + +```kotlin +fun asStrict() +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 添加描述 `Member` 类型包含 `strict`。 \ No newline at end of file diff --git a/docs/api/public/PackageParam.md b/docs/api/public/PackageParam.md new file mode 100644 index 00000000..17cd122f --- /dev/null +++ b/docs/api/public/PackageParam.md @@ -0,0 +1,433 @@ +## PackageParam [class] + +```kotlin +open class PackageParam(private var wrapper: PackageParamWrapper?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 装载 Hook 的目标 APP 入口对象实现类。 + +### appClassLoader [field] + +```kotlin +val appClassLoader:ClassLoader +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook APP 的 `ClassLoader`。 + +### appInfo [field] + +```kotlin +val appInfo: ApplicationInfo +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook APP 的 `ApplicationInfo`。 + +### appContext [field] + +```kotlin +val appContext: Application +``` + +变更记录 + +`v1.0.72` `新增` + +功能描述 + +> 获取当前 Hook APP 的 `Application`。 + +### processName [field] + +```kotlin +val processName: String +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook APP 的进程名称。 + +### packageName [field] + +```kotlin +val packageName: String +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook APP 的包名。 + +### isFirstApplication [field] + +```kotlin +val isFirstApplication: Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前 Hook APP 是否为第一个 `Application`。 + +### mainProcessName [field] + +```kotlin +val mainProcessName: String +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 获取当前 Hook APP 的主进程名称。 + +其对应的就是 `packageName`。 + +### prefs [field] + +```kotlin +val prefs: YukiHookModulePrefs +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获得当前使用的存取数据对象缓存实例。 + +### prefs [method] + +```kotlin +fun prefs(name: String): YukiHookModulePrefs +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获得当前使用的存取数据对象缓存实例。 + +你可以通过 `name` 来自定义 Sp 存储的名称。 + +### loadApp [method] + +```kotlin +fun loadApp(name: String, initiate: PackageParam.() -> Unit) +``` + +```kotlin +fun loadApp(name: String, hooker: YukiBaseHooker) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 装载并 Hook 指定包名的 APP。 + +`name` 为 APP 的包名,后方的两个参数一个可作为 `lambda` 方法体使用,一个可以直接装载子 Hooker。 + +### withProcess [method] + +```kotlin +fun withProcess(name: String, initiate: PackageParam.() -> Unit) +``` + +```kotlin +fun withProcess(name: String, hooker: YukiBaseHooker) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 装载并 Hook APP 的指定进程。 + +`name` 为 APP 的进程名称,后方的两个参数一个可作为 `lambda` 方法体使用,一个可以直接装载子 Hooker。 + +### loadHooker [method] + +```kotlin +fun loadHooker(hooker: YukiBaseHooker) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 装载 Hook 子类。 + +你可以填入 `hooker` 在 Hooker 中继续装载 Hooker。 + +### clazz [field] + +```kotlin +val String.clazz: Class<*> +``` + +```kotlin +val VariousClass.clazz: Class<*> +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 字符串、`VariousClass` 转换为实体类。 + +功能示例 + +你可以轻松地将 `String` 类型的 `Class` 包名转为 `Class` 实例。 + +> 示例如下 + +```kotlin +"com.example.demo.DemoClass".clazz +``` + +为了美观,你可以把字符串用 `(` `)` 括起来。 + +> 示例如下 + +```kotlin +("com.example.demo.DemoClass").clazz +``` + +你还可以创建一个 `VariousClass`,并转换为实体类。 + +`VariousClass` 会枚举所有设置的 `Class` 并最终获得完全匹配的那一个。 + +> 示例如下 + +```kotlin +VariousClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2").clazz +``` + +### hasClass [field] + +```kotlin +val String.hasClass: Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 通过字符串使用当前 `appClassLoader` 查找类是否存在。 + +功能示例 + +你可以轻松的使用此方法判断字符串中的类是否存在。 + +> 示例如下 + +```kotlin +if("com.example.demo.DemoClass".hasClass) { + // Your code here. +} +``` + +### findClass [method] + +```kotlin +fun findClass(name: String): HookClass +``` + +```kotlin +fun findClass(vararg name: String): VariousClass +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +移除了 ~~`findClass(various: VariousClass)`~~ 方法 + +功能描述 + +> 通过完整包名+名称查找需要被 Hook 的 `Class`。 + +功能示例 + +你可以使用三种方式查找你需要 Hook 的目标 `Class`。 + +你可以直接将被查找的 `Class` 完整包名+名称填入 `name` 中。 + +> 示例如下 + +```kotlin +findClass(name = "com.example.demo.DemoClass") +``` + +若你不确定多个版本的 `Class` 以及不同名称,你可以将多个完整包名+名称填入 `name` 中。 + +> 示例如下 + +```kotlin +findClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2", "com.example.demo.DemoClass3") +``` + +你还可以创建一个 `VariousClass`,将 `Class` 的完整包名+名称填入 `VariousClass` 的 `name` 中并填入 `various` 参数中。 + +> 示例如下 + +```kotlin +val variousClass = VariousClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2", "com.example.demo.DemoClass3") +``` + +### hook [method] + +```kotlin +fun String.hook(isUseAppClassLoader: Boolean, initiate: YukiHookCreater.() -> Unit): YukiHookCreater.Result +``` + +```kotlin +fun Class<*>.hook(isUseAppClassLoader: Boolean, initiate: YukiHookCreater.() -> Unit): YukiHookCreater.Result +``` + +```kotlin +fun VariousClass.hook(isUseAppClassLoader: Boolean, initiate: YukiHookCreater.() -> Unit): YukiHookCreater.Result +``` + +```kotlin +fun HookClass.hook(isUseAppClassLoader: Boolean, initiate: YukiHookCreater.() -> Unit): YukiHookCreater.Result +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +新增 `VariousClass` 的直接调用 `hook` 方法 + +`v1.0.2` `修改` + +新增 `String` 的直接调用 `hook` 方法 + +`v1.0.3` `修改` + +新增 `YukiHookCreater.Result` 返回值 + +`v1.0.70` `修改` + +新增 `isUseAppClassLoader` 参数 + +功能描述 + +> 这是一切 Hook 的入口创建方法,Hook 方法、构造类。 + +功能示例 + +如你所见,Hook 方法体的创建可使用 4 种方式。 + +通过字符串类名得到 `HookClass` 实例进行创建。 + +> 示例如下 + +```kotlin +("com.example.demo.DemoClass").hook { + // Your code here. +} + +``` +通过 `findClass` 得到 `HookClass` 实例进行创建。 + +> 示例如下 + +```kotlin +findClass(name = "com.example.demo.DemoClass").hook { + // Your code here. +} +``` + +使用 `stub` 或直接拿到 `Class` 实例进行创建。 + +> 示例如下 + +```kotlin +Activity::class.java.hook { + // Your code here. +} +``` + +使用 `VariousClass` 实例进行创建。 + +> 示例如下 + +```kotlin +VariousClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2").hook { + // Your code here. +} +``` + +或者直接使用可变字符串数组进行创建。 + +> 示例如下 + +```kotlin +findClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2").hook { + // Your code here. +} +``` + +!> 以下是关于 Hook 目标 Class 的一个特殊情况说明。 + +若你 Hook 的 `Class` 实例的 `ClassLoader` 并不是当前的 `appClassLoader`,那么你需要做一下小的调整。 + +在 `hook` 方法中加入 `isUseAppClassLoader = false`,这样,你的 `Class` 就不会被重新绑定到 `appClassLoader` 了。 + +此方案适用于目标 `Class` 无法在当前 `appClassLoader` 中被得到但可以得到 `Class` 实例的情况。 + +> 示例如下 + +```kotlin +YourClass.hook(isUseAppClassLoader = false) { + // Your code here. +} +``` \ No newline at end of file diff --git a/docs/api/public/PrefsData.md b/docs/api/public/PrefsData.md new file mode 100644 index 00000000..666fce96 --- /dev/null +++ b/docs/api/public/PrefsData.md @@ -0,0 +1,59 @@ +## PrefsData [class] + +```kotlin +data class PrefsData(var key: String, var value: T) +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 键值对存储构造类。 + +这个类是对 `YukiHookModulePrefs` 的一个扩展用法。 + +功能示例 + +建立一个模板类定义模块与宿主需要使用的键值数据。 + +> 示例如下 + +```kotlin +object DataConst { + + val TEST_KV_DATA_1 = PrefsData("test_data_1", "defalut value") + val TEST_KV_DATA_2 = PrefsData("test_data_2", false) + val TEST_KV_DATA_3 = PrefsData("test_data_3", 0) +} +``` + +键值数据定义后,你就可以方便地在模块和宿主中调用所需要的数据。 + +> 模块示例如下 + +```kotlin +// 读取 +val data = modulePrefs.get(DataConst.TEST_KV_DATA_1) +// 写入 +modulePrefs.put(DataConst.TEST_KV_DATA_1, "written value") +``` + +> 宿主示例如下 + +```kotlin +// 读取 String +val dataString = prefs.get(DataConst.TEST_KV_DATA_1) +// 读取 Boolean +val dataBoolean = prefs.get(DataConst.TEST_KV_DATA_2) +``` + +你依然可以不使用模板定义的默认值,随时修改你的默认值。 + +> 示例如下 + +```kotlin +// 读取 - 此时 data 取到的默认值将会是 2 - 并不是模板提供的 0 +val data = prefs.get(DataConst.TEST_KV_DATA_3, 2) +``` \ No newline at end of file diff --git a/docs/api/public/ReflectionFactory.md b/docs/api/public/ReflectionFactory.md new file mode 100644 index 00000000..910ec892 --- /dev/null +++ b/docs/api/public/ReflectionFactory.md @@ -0,0 +1,383 @@ +## ReflectionFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是自定义 `Member` 和 `Class` 相关功能的查找匹配以及 `invoke` 的封装类。 + +### hasClass [field] + +```kotlin +val String.hasClass: Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 通过字符串查找类是否存在。 + +功能示例 + +你可以轻松的使用此方法判断字符串中的类是否存在。 + +!> 此查找仅限使用当前的 `ClassLoader`,若要指定 `ClassLoader` 请使用下方的 `hasClass` 同名方法。 + +> 示例如下 + +```kotlin +if("com.example.demo.DemoClass".hasClass) { + // Your code here. +} +``` + +### hookClass [field] + +```kotlin +val Class<*>.hookClass: HookClass +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 将 `Class` 转换为 `HookClass`。 + +### normalClass [field] + +```kotlin +val HookClass.normalClass: Class<*>? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 将 `HookClass` 转换为 `Class`。 + +### classOf [method] + +```kotlin +fun classOf(name: String, loader: ClassLoader?): Class<*> +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 通过字符串使用指定的 `ClassLoader` 转换为实体类。 + +功能示例 + +你可以直接填写你要查找的目标 `Class`,必须在当前 `ClassLoader` 下存在。 + +> 示例如下 + +```kotlin +classOf(name = "com.example.demo.DemoClass") +``` + +你还可以自定义 `Class` 所在的 `ClassLoader`。 + +> 示例如下 + +```kotlin +classOf(name = "com.example.demo.DemoClass", classLoader) +``` + +### hasClass [method] + +```kotlin +fun String.hasClass(loader: ClassLoader?): Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 通过字符串使用指定的 `ClassLoader` 查找类是否存在。 + +### hasField [method] + +```kotlin +fun Class<*>.hasField(initiate: FieldFinder.() -> Unit): Boolean +``` + +变更记录 + +`v1.0.4` `新增` + +`v1.0.67` `修改` + +合并到 `FieldFinder` + +功能描述 + +> 查找变量是否存在。 + +### hasMethod [method] + +```kotlin +fun Class<*>.hasMethod(initiate: MethodFinder.() -> Unit): Boolean +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +新增 `returnType` 参数 + +`v1.0.67` `修改` + +合并到 `MethodFinder` + +功能描述 + +> 查找方法是否存在。 + +### hasConstructor [method] + +```kotlin +fun Class<*>.hasConstructor(initiate: ConstructorFinder.() -> Unit): Boolean +``` + +变更记录 + +`v1.0.2` `新增` + +`v1.0.67` `修改` + +合并到 `ConstructorFinder` + +功能描述 + +> 查找构造方法是否存在。 + +### hasModifiers [method] + +```kotlin +fun Member.hasModifiers(initiate: ModifierRules.() -> Unit): Boolean +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 查询 `Member` 中匹配的描述符。 + +### ~~obtainStaticFieldAny [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `移除` + +### ~~obtainFieldAny [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `移除` + +### ~~modifyStaticField [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `移除` + +### ~~modifyField [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `移除` + +### field [method] + +```kotlin +fun Class<*>.field(initiate: FieldFinder.() -> Unit): FieldFinder.Result +``` + +变更记录 + +`v1.0.2` `新增` + +功能描述 + +> 查找并得到变量。 + +### method [method] + +```kotlin +fun Class<*>.method(initiate: MethodFinder.() -> Unit): MethodFinder.Result +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +~~`obtainMethod`~~ 更名为 `method` + +新增 `returnType` 参数 + +`v1.0.2` `修改` + +合并到 `MethodFinder` 方法体。 + +功能描述 + +> 查找并得到方法。 + +### constructor [method] + +```kotlin +fun Class<*>.constructor(initiate: ConstructorFinder.() -> Unit): ConstructorFinder.Result +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +~~`obtainConstructor`~~ 更名为 `constructor` + +`v1.0.2` `修改` + +合并到 `ConstructorFinder` 方法体。 + +功能描述 + +> 查找并得到构造类。 + +### ~~callStatic [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +~~`invokeStatic`~~ 更名为 `callStatic` + +`v1.0.2` `移除` + +### ~~call [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.1` `修改` + +~~`invokeAny`~~ 更名为 `call` + +`v1.0.2` `移除` + +### current [method] + +```kotlin +inline fun T.current(initiate: CurrentClass.() -> Unit): T +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 获得当前实例的类操作对象。 + +### buildOf [method] + +```kotlin +fun Class<*>.buildOf(vararg param: Any?, initiate: ConstructorFinder.() -> Unit): T? +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 通过构造方法创建新实例,指定类型 `T`。 + +### buildOfAny [method] + +```kotlin +fun Class<*>.buildOfAny(vararg param: Any?, initiate: ConstructorFinder.() -> Unit): Any? +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 通过构造方法创建新实例,任意类型 `Any`。 + +### allMethods [method] + +```kotlin +fun Class<*>.allMethods(callback: (index: Int, method: Method) -> Unit) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 遍历当前类中的所有方法。 + +### allConstructors [method] + +```kotlin +fun Class<*>.allConstructors(callback: (index: Int, constructor: Constructor<*>) -> Unit) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 遍历当前类中的所有构造方法。 + +### allFields [method] + +```kotlin +fun Class<*>.allFields(callback: (index: Int, field: Field) -> Unit) +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 遍历当前类中的所有变量。 \ No newline at end of file diff --git a/docs/api/public/VariableTypeFactory.md b/docs/api/public/VariableTypeFactory.md new file mode 100644 index 00000000..4f5fede5 --- /dev/null +++ b/docs/api/public/VariableTypeFactory.md @@ -0,0 +1,11 @@ +## VariableTypeFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是一个预置 Hook 类型的常量类,主要为 `Java` 相关基本变量类型的 `Class` 内容,跟随版本更新会逐一进行增加。 + +详情可 [点击这里](https://github.com/fankes/YukiHookAPI/blob/master/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/type/java/VariableTypeFactory.kt) 进行查看。 \ No newline at end of file diff --git a/docs/api/public/VariousClass.md b/docs/api/public/VariousClass.md new file mode 100644 index 00000000..1d510b34 --- /dev/null +++ b/docs/api/public/VariousClass.md @@ -0,0 +1,29 @@ +## VariousClass [class] + +```kotlin +class VariousClass(vararg var name: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是一个不确定性 `Class` 类名装载器,通过 `name` 装载 `Class` 名称数组。 + +### get [method] + +```kotlin +fun get(loader: ClassLoader? = null): Class<*> +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 获取匹配的实体类。 + +使用当前 `loader` 装载目标 `Class`。 \ No newline at end of file diff --git a/docs/api/public/ViewTypeFactory.md b/docs/api/public/ViewTypeFactory.md new file mode 100644 index 00000000..596b7d57 --- /dev/null +++ b/docs/api/public/ViewTypeFactory.md @@ -0,0 +1,11 @@ +## ViewTypeFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是一个预置 Hook 类型的常量类,主要为 `Android` 相关 `Widget` 的 `Class` 内容,跟随版本更新会逐一进行增加。 + +详情可 [点击这里](https://github.com/fankes/YukiHookAPI/blob/master/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/type/android/ViewTypeFactory.kt) 进行查看。 \ No newline at end of file diff --git a/docs/api/public/YukiBaseHooker.md b/docs/api/public/YukiBaseHooker.md new file mode 100644 index 00000000..b80ce9b9 --- /dev/null +++ b/docs/api/public/YukiBaseHooker.md @@ -0,0 +1,27 @@ +## YukiBaseHooker [class] + +```kotlin +abstract class YukiBaseHooker : PackageParam() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `YukiHookAPI` 的子类 Hooker 实现。 + +### onHook [method] + +```kotlin +fun onHook() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 子类 Hook 开始。 \ No newline at end of file diff --git a/docs/api/public/YukiHookAPI.md b/docs/api/public/YukiHookAPI.md new file mode 100644 index 00000000..bbca7212 --- /dev/null +++ b/docs/api/public/YukiHookAPI.md @@ -0,0 +1,296 @@ +## YukiHookAPI [object] + +```kotlin +object YukiHookAPI +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是 `YukiHookAPI` 的 API 调用总类,Hook 相关功能的开始、Hook 相关功能的配置都在这里。 + +### API_VERSION_NAME [field] + +```kotlin +const val API_VERSION_NAME: String +``` + +变更记录 + +`v1.0.4` `新增` + +功能描述 + +> 获取当前 `YukiHookAPI` 的版本。 + +### API_VERSION_CODE [field] + +```kotlin +const val API_VERSION_CODE: Int +``` + +变更记录 + +`v1.0.4` `新增` + +功能描述 + +> 获取当前 `YukiHookAPI` 的版本号。 + +### executorName [field] + +```kotlin +val executorName: String +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 获取当前 Hook 框架的名称。 + +无法获取会返回 `unknown`,`XposedBridge` 不存在会返回 `invalid`。 + +### executorVersion [field] + +```kotlin +val executorVersion: Int +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 获取当前 Hook 框架的版本。 + +无法获取会返回 `-1`。 + +### Configs [object] + +```kotlin +object Configs +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 对 API 相关功能的配置类。 + +#### debugTag [field] + +```kotlin +var debugTag: String +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 模块在调用 `logger` 时打印的日志 `TAG` 名称。 + +你可以方便地进行自定义,并可以在 `Logcat` 和 `XposedBridge.log` 中找到它们。 + +#### isDebug [field] + +```kotlin +var isDebug: Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 是否启用 DEBUG 模式。 + +默认为开启状态,开启后模块将会向 `Logcat` 和 `XposedBridge.log` 打印详细的 Hook 日志,关闭后仅会打印 `E` 级别的日志。 + +#### isAllowPrintingLogs [field] + +```kotlin +var isAllowPrintingLogs: Boolean +``` + +变更记录 + +`v1.0.4` `新增` + +功能描述 + +> 是否启用调试日志的输出功能。 + +!> 关闭后将会停用 `YukiHookAPI` 对全部日志的输出,但是不影响当你手动调用日志方法输出日志。 + +#### isEnableModulePrefsCache [field] + +```kotlin +var isEnableModulePrefsCache: Boolean +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 是否启用 `YukiHookModulePrefs` 的键值缓存功能。 + +为防止内存复用过高问题,此功能默认启用。 + +你可以手动在 `YukiHookModulePrefs` 中自由开启和关闭缓存功能以及清除缓存。 + +#### isEnableMemberCache [field] + +```kotlin +var isEnableMemberCache: Boolean +``` + +变更记录 + +`v1.0.68` `新增` + +功能描述 + +> 是否启用 `Member` 缓存功能。 + +为防止 `Member` 复用过高造成的系统 GC 问题,此功能默认启用。 + +启用后会缓存已经找到的 `Class`、`Method`、`Constructor`、`Field`。 + +缓存的 `Member` 都将处于 `MemberCacheStore` 的全局静态实例中。 + +推荐使用 `MethodFinder`、`ConstructorFinder`、`FieldFinder` 来获取 `Member`。 + +除非缓存的 `Member` 发生了混淆的问题,例如使用 R8 混淆后的 APP 的目标 `Member`,否则建议启用。 + +### configs [method] + +```kotlin +fun configs(initiate: Configs.() -> Unit) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 对 `Configs` 类实现了一个 `lambda` 方法体。 + +你可以轻松的调用它进行配置。 + +功能示例 + +你可以在 `HookEntryClass` 的 `onInit` 方法中调用 `configs` 方法完成对 API 的功能配置,实时生效。 + +> 示例如下 + +```kotlin +class HookEntryClass : YukiHookXposedInitProxy { + + override fun onInit() { + YukiHookAPI.configs { + debugTag = "YukiHookAPI" + isDebug = true + isAllowPrintingLogs = true + isEnableModulePrefsCache = true + isEnableMemberCache = true + } + } + + override fun onHook() { + // Your code here. + } +} +``` + +若觉得上面的写法不美观,你还可以写得更加简洁。 + +> 示例如下 + +```kotlin +class HookEntryClass : YukiHookXposedInitProxy { + + override fun onInit() = configs { + debugTag = "YukiHookAPI" + isDebug = true + isAllowPrintingLogs = true + isEnableModulePrefsCache = true + isEnableMemberCache = true + } + + override fun onHook() { + // Your code here. + } +} +``` + +你也可以不通过 `configs` 方法,直接进行配置。 + +> 示例如下 + +```kotlin +class HookEntryClass : YukiHookXposedInitProxy { + + override fun onInit() { + YukiHookAPI.Configs.debugTag = "YukiHookAPI" + YukiHookAPI.Configs.isDebug = true + YukiHookAPI.Configs.isAllowPrintingLogs = true + YukiHookAPI.Configs.isEnableModulePrefsCache = true + YukiHookAPI.Configs.isEnableMemberCache = true + } + + override fun onHook() { + // Your code here. + } +} +``` + +### encase [method] + +```kotlin +fun encase(initiate: PackageParam.() -> Unit) +``` + +```kotlin +fun encase(vararg hooker: YukiBaseHooker) +``` + +```kotlin +fun encase(baseContext: Context?, initiate: PackageParam.() -> Unit) +``` + +```kotlin +fun encase(baseContext: Context?, vararg hooker: YukiBaseHooker) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 装载 Hook 入口的核心方法。 + +功能示例 + +详情请参考 + +- [通过 Lambda 创建](config/api-example?id=通过-lambda-创建) + +- [通过自定义 Hooker 创建](config/api-example?id=通过自定义-hooker-创建) + +- [作为 Hook API 使用需要注意的地方](config/api-example?id=作为-hook-api-使用需要注意的地方) \ No newline at end of file diff --git a/docs/api/public/YukiHookCreater.md b/docs/api/public/YukiHookCreater.md new file mode 100644 index 00000000..5601e3ae --- /dev/null +++ b/docs/api/public/YukiHookCreater.md @@ -0,0 +1,694 @@ +## YukiHookCreater [class] + +```kotlin +class YukiHookCreater(private val packageParam: PackageParam, private val hookClass: HookClass) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> `YukiHookAPI` 核心 Hook 实现类。 + +### instanceClass [field] + +```kotlin +val instanceClass: Class<*> +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.2` `修改` + +~~`thisClass`~~ 更名为 `instanceClass` + +功能描述 + +> 得到当前被 Hook 的 `Class`。 + +!> 不推荐直接使用,万一得不到 `Class` 对象则会无法处理异常导致崩溃。 + +### injectMember [method] + +```kotlin +fun injectMember(tag: String, initiate: MemberHookCreater.() -> Unit): MemberHookCreater.Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 注入要 Hook 的方法、构造类。 + +功能示例 + +你可以注入任意方法与构造类,使用 `injectMember` 即可创建一个 `Hook` 对象。 + +> 示例如下 + +```kotlin +injectMember { + // Your code here. +} +``` + +你还可以自定义 `tag`,方便你在调试的时候能够区分你的 `Hook` 对象。 + +> 示例如下 + +```kotlin +injectMember(tag = "KuriharaYuki") { + // Your code here. +} +``` + +### MemberHookCreater [class] + +```kotlin +inner class MemberHookCreater(var tag: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> Hook 核心功能实现类,查找和处理需要 Hook 的方法、构造类。 + +#### member [field] + +```kotlin +var member: Member? +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 手动指定要 Hook 的方法、构造类。 + +!> 不建议使用此方法设置目标需要 Hook 的 `Member` 对象,你可以使用 `method` 或 `constructor` 方法。 + +功能示例 + +你可以调用 `instanceClass` 来手动查询要 Hook 的方法。 + +> 示例如下 + +```kotlin +injectMember { + member = instanceClass.getMethod("test", StringType) + beforeHook {} + afterHook {} +} +``` + +#### allMethods [method] + +```kotlin +fun allMethods(name: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 查找并 Hook 当前 `Class` 中指定 `name` 的全部方法。 + +功能示例 + +使用此方法可将当前类的全部同名方法进行批量 Hook。 + +!> 无法准确处理每个方法的 `param`,建议使用 `method` 对每个方法单独 Hook。 + +> 示例如下 + +```kotlin +injectMember { + allMethods(name = "test") + beforeHook {} + afterHook {} +} +``` + +#### allConstructors [method] + +```kotlin +fun allConstructors() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 查找并 Hook 当前 `Class` 中的全部构造方法。 + +功能示例 + +使用此方法可将当前类的全部构造方法进行批量 Hook。 + +!> 无法准确处理每个构造方法的 `param`,建议使用 `constructor` 对每个构造方法单独 Hook。 + +> 示例如下 + +```kotlin +injectMember { + allConstructors() + beforeHook {} + afterHook {} +} +``` + +#### method [method] + +```kotlin +fun method(initiate: MethodFinder.() -> Unit): MethodFinder.Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 查找当前 `Class` 需要 Hook 的方法。 + +功能示例 + +你可参考 [MethodFinder](#methodfinder-class) 查看详细用法。 + +> 示例如下 + +```kotlin +injectMember { + method { + name = "test" + param(StringType) + returnType = UnitType + } + beforeHook {} + afterHook {} +} +``` + +#### constructor [method] + +```kotlin +fun constructor(initiate: ConstructorFinder.() -> Unit): ConstructorFinder.Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 查找当前 `Class` 需要 Hook 的构造方法。 + +功能示例 + +你可参考 [ConstructorFinder](#constructorfinder-class) 查看详细用法。 + +> 示例如下 + +```kotlin +injectMember { + constructor { param(StringType) } + beforeHook {} + afterHook {} +} +``` + +#### field [method] + +```kotlin +fun HookParam.field(initiate: FieldFinder.() -> Unit): FieldFinder.Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 使用当前 `hookClass` 查找并得到 `Field`。 + +功能示例 + +你可参考 [FieldFinder](#fieldfinder-class) 查看详细用法。 + +> 示例如下 + +```kotlin +injectMember { + method { + name = "test" + param(StringType) + returnType = UnitType + } + afterHook { + field { + name = "isSweet" + type = BooleanType + }.get(instance).setTrue() + } +} +``` + +#### method [method] + +```kotlin +fun HookParam.method(initiate: MethodFinder.() -> Unit): MethodFinder.Result +``` + +变更记录 + +`v1.0.2` `添加` + +功能描述 + +> 使用当前 `hookClass` 查找并得到方法。 + +#### constructor [method] + +```kotlin +fun HookParam.constructor(initiate: ConstructorFinder.() -> Unit): ConstructorFinder.Result +``` + +变更记录 + +`v1.0.2` `添加` + +功能描述 + +> 使用当前 `hookClass` 查找并得到构造方法。 + +#### beforeHook [method] + +```kotlin +fun beforeHook(initiate: HookParam.() -> Unit) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 在方法执行完成前 Hook。 + +#### afterHook [method] + +```kotlin +fun afterHook(initiate: HookParam.() -> Unit) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 在方法执行完成后 Hook。 + +#### replaceAny [method] + +```kotlin +fun replaceAny(initiate: HookParam.() -> Any?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 拦截并替换此方法内容,给出返回值。 + +#### replaceUnit [method] + +```kotlin +fun replaceUnit(initiate: HookParam.() -> Unit) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 拦截并替换此方法内容,没有返回值,可以称为 `Void`。 + +#### replaceTo [method] + +```kotlin +fun replaceTo(any: Any?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 拦截并替换方法返回值。 + +#### replaceToTrue [method] + +```kotlin +fun replaceToTrue() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 拦截并替换方法返回值为 `true`。 + +!> 确保替换方法的返回对象为 `Boolean`。 + +#### replaceToFalse [method] + +```kotlin +fun replaceToFalse() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 拦截并替换方法返回值为 `false`。 + +!> 确保替换方法的返回对象为 `Boolean`。 + +#### intercept [method] + +```kotlin +fun intercept() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 拦截此方法。 + +!> 这将会禁止此方法执行并返回 `null`。 + +#### Result [class] + +```kotlin +inner class Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听 Hook 结果实现类。 + +##### result [method] + +```kotlin +fun result(initiate: Result.() -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +`v1.0.5` `修改` + +~~`failures`~~ 修改为 `result` + +功能描述 + +> 创建监听失败事件方法体。 + +功能示例 + +你可以使用此方法为 `Result` 类创建 `lambda` 方法体。 + +> 示例如下 + +```kotlin +injectMember { + // Your code here. +}.result { + onHooked {} + ignoredConductFailure() + onHookingFailure {} + // ... +} +``` + +##### by [method] + +```kotlin +fun by(initiate: () -> Boolean): Result +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 添加执行 Hook 需要满足的条件,不满足条件将直接停止 Hook。 + +##### onHooked [method] + +```kotlin +fun onHooked(initiate: (Member) -> Unit): Result +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 监听 `member` Hook 成功的回调方法。 + +在首次 Hook 成功后回调。 + +##### onNoSuchMemberFailure [method] + +```kotlin +fun onNoSuchMemberFailure(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 监听 `member` 不存在发生错误的回调方法。 + +##### onConductFailure [method] + +```kotlin +fun onConductFailure(initiate: (HookParam, Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听 Hook 进行过程中发生错误的回调方法。 + +##### onHookingFailure [method] + +```kotlin +fun onHookingFailure(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听 Hook 开始时发生的错误的回调方法。 + +##### onAllFailure [method] + +```kotlin +fun onAllFailure(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 监听全部 Hook 过程发生错误的回调方法。 + +##### ignoredNoSuchMemberFailure [method] + +```kotlin +fun ignoredNoSuchMemberFailure(): Result +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 忽略 `member` 不存在发生的错误。 + +##### ignoredConductFailure [method] + +```kotlin +fun ignoredConductFailure(): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 忽略 Hook 进行过程中发生的错误。 + +##### ignoredHookingFailure [method] + +```kotlin +fun ignoredHookingFailure(): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 忽略 Hook 开始时发生的错误。 + +##### ignoredAllFailure [method] + +```kotlin +fun ignoredAllFailure(): Result +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 忽略全部 Hook 过程发生的错误。 + +### Result [class] + +```kotlin +inner class Result +``` + +变更记录 + +`v1.0.3` `新增` + +功能描述 + +> 监听全部 Hook 结果实现类。 + +#### result [method] + +```kotlin +fun result(initiate: Result.() -> Unit): Result +``` + +变更记录 + +`v1.0.3` `新增` + +`v1.0.5` `修改` + +~~`failures`~~ 修改为 `result` + +功能描述 + +> 创建监听事件方法体。 + +#### by [method] + +```kotlin +fun by(initiate: () -> Boolean): Result +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 添加执行 Hook 需要满足的条件,不满足条件将直接停止 Hook。 + +#### onPrepareHook [method] + +```kotlin +fun onPrepareHook(initiate: () -> Unit): Result +``` + +变更记录 + +`v1.0.70` `新增` + +功能描述 + +> 监听 `hookClass` 存在时准备开始 Hook 的操作。 + +#### onHookClassNotFoundFailure [method] + +```kotlin +fun onHookClassNotFoundFailure(initiate: (Throwable) -> Unit): Result +``` + +变更记录 + +`v1.0.3` `新增` + +功能描述 + +> 监听 `hookClass` 找不到时发生错误的回调方法。 + +#### ignoredHookClassNotFoundFailure [method] + +```kotlin +fun ignoredHookClassNotFoundFailure(): Result +``` + +变更记录 + +`v1.0.3` `新增` + +功能描述 + +> 忽略 `hookClass` 找不到时出现的错误。 \ No newline at end of file diff --git a/docs/api/public/YukiHookFactory.md b/docs/api/public/YukiHookFactory.md new file mode 100644 index 00000000..6c643be6 --- /dev/null +++ b/docs/api/public/YukiHookFactory.md @@ -0,0 +1,125 @@ +## YukiHookFactory [kt] + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是 `YukiHookAPI` 相关 `lambda` 方法的封装类以及部分 API 用法。 + +### configs [method] + +```kotlin +fun YukiHookXposedInitProxy.configs(initiate: YukiHookAPI.Configs.() -> Unit) +``` + +变更记录 + +`v1.0.1` `新增` + +功能描述 + +> 在 `YukiHookXposedInitProxy` 中配置 `Configs`。 + +### encase [method] + +```kotlin +fun YukiHookXposedInitProxy.encase(initiate: PackageParam.() -> Unit) +``` + +```kotlin +fun YukiHookXposedInitProxy.encase(vararg hooker: YukiBaseHooker) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 在 `YukiHookXposedInitProxy` 中装载 `YukiHookAPI`。 + +### modulePrefs [field] + +```kotlin +val Context.modulePrefs: YukiHookModulePrefs +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取模块的存取对象。 + +### modulePrefs [method] + +```kotlin +fun Context.modulePrefs(name: String): YukiHookModulePrefs +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取模块的存取对象,可设置 `name` 为自定义 Sp 存储名称。 + +### processName [field] + +```kotlin +val Context.processName: String +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取当前进程名称。 + +### isModuleActive [field] + +```kotlin +val Context.isModuleActive: Boolean +``` + +变更记录 + +`v1.0.6` `新增` + +功能描述 + +> 判断模块是否在 Xposed 或太极、无极中激活。 + +### isXposedModuleActive [field] + +```kotlin +val Any?.isXposedModuleActive: Boolean +``` + +变更记录 + +`v1.0.6` `新增` + +功能描述 + +> 仅判断模块是否在 Xposed 中激活。 + +### isTaiChiModuleActive [field] + +```kotlin +val Context.isTaiChiModuleActive: Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 仅判断模块是否在太极、无极中激活。 \ No newline at end of file diff --git a/docs/api/public/YukiHookModulePrefs.md b/docs/api/public/YukiHookModulePrefs.md new file mode 100644 index 00000000..c735a9c4 --- /dev/null +++ b/docs/api/public/YukiHookModulePrefs.md @@ -0,0 +1,309 @@ +## YukiHookModulePrefs [class] + +```kotlin +class YukiHookModulePrefs(private val context: Context?) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 实现 Xposed 模块的数据存取,对接 `SharedPreferences` 和 `XSharedPreferences`。 + +在不同环境智能选择存取使用的对象。 + +!> 请注意此功能为实验性功能,仅在 LSPosed 环境测试通过,EdXposed 理论也可以使用但不再推荐。 + +使用 LSPosed 环境请在 `AndroidManifests.xml` 中将 `xposedminversion` 最低设置为 `93`。 + +详见 [New XSharedPreferences](https://github.com/LSPosed/LSPosed/wiki/New-XSharedPreferences#for-the-module)。 + +未使用 LSPosed 环境请将你的模块 `API` 降至 `26` 以下,`YukiHookAPI` 将会尝试使用 `makeWorldReadable` 但仍有可能不成功。 + +太极请参阅 [文件权限/配置/XSharedPreference](https://taichi.cool/zh/doc/for-xposed-dev.html#文件权限-配置-xsharedpreference)。 + +!> 当你在 Xposed 模块中存取数据的时候 `context` 必须不能是空的。 + +### name [method] + +```kotlin +fun name(name: String): YukiHookModulePrefs +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 自定义 Sp 存储名称。 + +功能示例 + +在 `Activity` 中的使用方法。 + +> 示例如下 + +```kotlin +modulePrefs("custom_name").getString("custom_key") +``` + +在 Xposed 模块环境 `PackageParam` 中的使用方法。 + +> 示例如下 + +```kotlin +prefs("custom_name").getString("custom_key") +``` + +### direct [method] + +```kotlin +fun direct(): YukiHookModulePrefs +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 忽略缓存直接读取键值。 + +无论是否开启 `YukiHookAPI.Configs.isEnableModulePrefsCache`。 + +仅在 `XSharedPreferences` 下生效。 + +### getString [method] + +```kotlin +fun getString(key: String, value: String): String +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取 `String` 键值。 + +### getBoolean [method] + +```kotlin +fun getBoolean(key: String, value: Boolean): Boolean +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取 `Boolean` 键值。 + +### getInt [method] + +```kotlin +fun getInt(key: String, value: Int): Int +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取 `Int` 键值。 + +### getLong [method] + +```kotlin +fun getLong(key: String, value: Long): Long +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取 `Long` 键值。 + +### getFloat [method] + +```kotlin +fun getFloat(key: String, value: Float): Float +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 获取 `Float` 键值。 + +### remove [method] + +```kotlin +fun remove(key: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 移除全部包含 `key` 的存储数据。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### remove [method] + +```kotlin +inline fun remove(prefs: PrefsData) +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 移除 `PrefsData.key` 的存储数据。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### putString [method] + +```kotlin +fun putString(key: String, value: String) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 存储 `String` 键值。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### putBoolean [method] + +```kotlin +fun putBoolean(key: String, value: Boolean) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 存储 `Boolean` 键值。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### putInt [method] + +```kotlin +fun putInt(key: String, value: Int) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 存储 `Int` 键值。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### putLong [method] + +```kotlin +fun putLong(key: String, value: Long) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 存储 `Long` 键值。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### putFloat [method] + +```kotlin +fun putFloat(key: String, value: Float) +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 存储 `Float` 键值。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### get [method] + +```kotlin +inline fun get(prefs: PrefsData, value: T): T +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 智能获取指定类型的键值。 + +### put [method] + +```kotlin +inline fun put(prefs: PrefsData, value: T) +``` + +变更记录 + +`v1.0.67` `新增` + +功能描述 + +> 智能存储指定类型的键值。 + +!> 在 `XSharedPreferences` 环境下只读,无法使用。 + +### clearCache [method] + +```kotlin +fun clearCache() +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 清除 `XSharedPreferences` 中缓存的键值数据。 + +无论是否开启 `YukiHookAPI.Configs.isEnableModulePrefsCache`。 + +调用此方法将清除当前存储的全部键值缓存。 + +下次将从 `XSharedPreferences` 重新读取。 \ No newline at end of file diff --git a/docs/api/public/YukiHookModuleStatus.md b/docs/api/public/YukiHookModuleStatus.md new file mode 100644 index 00000000..e23333ce --- /dev/null +++ b/docs/api/public/YukiHookModuleStatus.md @@ -0,0 +1,55 @@ +## YukiHookModuleStatus [class] + +```kotlin +object YukiHookModuleStatus +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> 这是一个 Xposed 模块 Hook 状态类。 + +### executorName [field] + +```kotlin +val executorName: String +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 获取当前 Hook 框架的名称。 + +模块未激活会返回 `unknown`,获取过程发生错误会返回 `invalid`。 + +### executorVersion [field] + +```kotlin +val executorVersion: Int +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 获取当前 Hook 框架的版本。 + +模块未激活会返回 `-1`。 + +### ~~isActive [method]~~ + +变更记录 + +`v1.0` `添加` + +`v1.0.6` `作废` + +请使用 `isModuleActive`、`isXposedModuleActive` 或 `isTaiChiModuleActive` \ No newline at end of file diff --git a/docs/api/public/YukiHookXposedInitProxy.md b/docs/api/public/YukiHookXposedInitProxy.md new file mode 100644 index 00000000..7272b63b --- /dev/null +++ b/docs/api/public/YukiHookXposedInitProxy.md @@ -0,0 +1,45 @@ +## YukiHookXposedInitProxy [interface] + +```kotlin +interface YukiHookXposedInitProxy +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> YukiHookAPI 的 Xposed 装载 API 调用接口。 + +### onInit [method] + +```kotlin +fun onInit() +``` + +变更记录 + +`v1.0.5` `新增` + +功能描述 + +> 配置 `YukiHookAPI.Configs` 的初始化方法。 + +!> 在这里只能进行初始化配置,不能进行 Hook 操作。 + +此方法可选,你也可以选择不对 `YukiHookAPI.Configs` 进行配置。 + +### onHook [method] + +```kotlin +fun onHook() +``` + +变更记录 + +`v1.0` `添加` + +功能描述 + +> Xposed API 的模块装载调用入口方法。 \ No newline at end of file diff --git a/docs/config/api-example.md b/docs/config/api-example.md new file mode 100644 index 00000000..33c4ba54 --- /dev/null +++ b/docs/config/api-example.md @@ -0,0 +1,199 @@ +# API 基本配置 + +> 这里介绍了 `YukiHookAPI` 的基本配置方法。 + +## 功能配置 + +> 无论是 [作为 Xposed 模块使用](config/xposed-using) 还是 [作为 Hook API 使用](config/api-using),你都可以在 API 装载之前或装载过程中对 `YukiHookAPI` 进行配置。 + +### configs 方法 + +```kotlin +fun configs(initiate: Configs.() -> Unit) +``` + +`configs` 方法对 `Configs` 类实现了一个 `lambda` 方法体,你可以轻松地调用它进行配置。 + +你可以 [点击这里](api/document?id=configs-method) 查看有关用法的说明和示例。 + +## Hooker 配置 + +> 一个 Xposed 模块或 Hook API 最重要的地方就是 Hooker 的创建与使用,`YukiHookAPI` 提供了两种使用方法。 + +### 通过 Lambda 创建 + +> 这种方案是最简单的,如果你的模块功能不多,代码数量不大,不需要进行分类处理,推荐使用这种方式进行创建。 + +#### encase 方法 + +```kotlin +fun encase(initiate: PackageParam.() -> Unit) +``` + +`encase` 方法是 Hook 一切生命的开始,在一个模块或一个 Hook 过程中,`encase` 方法只能作用一次,用于创建 Hooker。 + +`PackageParam` 为宿主(目标 APP)的重要实例对象,通过 `PackageParam` 来实现对当前 Hook 作用对象的全部 Hook 操作。 + +你可以 [点击这里](api/document?id=packageparam-class) 了解其中的详细用法。 + +`encase` 方法可以在 `onHook` 方法中使用两种方案创建。 + +> 示例代码 1 + +```kotlin +YukiHookAPI.encase { + loadApp(name = "com.example.demo") { + findClass(name = "$packageName.DemoClass").hook { + // Your code here. + } + } +} +``` + +> 示例代码 2 + +```kotlin +encase { + loadApp(name = "com.example.demo") { + findClass(name = "$packageName.DemoClass").hook { + // Your code here. + } + } +} +``` + +在 `encase` 方法中进行你的 Hook 操作。 + +### 通过自定义 Hooker 创建 + +> 这种方案更加适用于大型项目,例如需要对 Hooker 进行分类或对 Hook 的作用对象进行分类。 + +#### encase 方法 + +```kotlin +fun encase(vararg hooker: YukiBaseHooker) +``` + +同样为 `encase` 方法,这里的方法可变数组参数 `hooker` 为创建入口提供了一个对象,你可以将所有继承于 `YukiBaseHooker` 的 Hooker 一次性进行装载。 + +#### YukiBaseHooker 用法 + +`YukiBaseHooker` 继承于 `PackageParam`,你需要将你的子 Hooker 继承于 `YukiBaseHooker`。 + +若要了解更多可 [点击这里](api/document?id=yukibasehooker-class) 进行查看。 + +> 示例如下 + +```kotlin +class CustomHooker : YukiBaseHooker() { + + override fun onHook() { + // Your code here. + } +} +``` + +!> 你无需再在继承于 `YukiBaseHooker` 的 `onHook` 方法中重新调用 `encase`,这是错误的,你应该直接开始编写你的 Hook 代码。 + +> 示例如下 + +```kotlin +class CustomHooker : YukiBaseHooker() { + + override fun onHook() { + loadApp(name = "com.example.demo1") { + findClass(name = "$packageName.DemoClass").hook { + // Your code here. + } + } + loadApp(name = "com.example.demo2") { + findClass(name = "$packageName.CustomClass").hook { + // Your code here. + } + } + } +} +``` + +作为子 Hooker 使用,你还可以在外部调用 `loadApp` 方法,然后在内部直接开始 Hook。 + +> 示例如下 + +```kotlin +class HookEntryClass : YukiHookXposedInitProxy { + + override fun onHook() = encase { + loadApp(name = "com.example.demo", ChildCustomHooker()) + } +} + +class ChildCustomHooker : YukiBaseHooker() { + + override fun onHook() { + findClass(name = "$packageName.DemoClass").hook { + // Your code here. + } + } +} +``` + +你可以使用 `loadHooker` 方法在子 Hooker 中多层装载另一个 Hooker,请按照你的喜好进行即可。 + +> 示例如下 + +```kotlin +class FirstHooker : YukiBaseHooker() { + + override fun onHook() { + findClass(name = "$packageName.DemoClass").hook { + // Your code here. + } + loadHooker(SecondHooker()) + loadHooker(ThirdHooker()) + } +} +``` + +搭建完全部 Hooker 后,你就可以在你的 `HookEntryClass` 入口类中的 `onHook` 方法中装载你的 Hooker 了。 + +> 示例如下 + +```kotlin +class HookEntryClass : YukiHookXposedInitProxy { + + override fun onHook() = + YukiHookAPI.encase(FirstHooker(), SecondHooker(), ThirdHooker() ...) +} +``` + +当然,我们同样可以对其进行简写。 + +> 示例如下 + +```kotlin +class HookEntryClass : YukiHookXposedInitProxy { + + override fun onHook() = encase(FirstHooker(), SecondHooker(), ThirdHooker() ...) +} +``` + +## 作为 Hook API 使用需要注意的地方 + +若你作为 Hook API 使用,那么你只需要在入口处对 `encase` 方法进行区分。 + +!> `encase` 方法对作为 Hook API 使用提供了两个完全一样的方法,但是比前两者仅多出一个参数 `baseContext`。 + +> 方法 1 + +```kotlin +fun encase(baseContext: Context?, initiate: PackageParam.() -> Unit) +``` +> 方法 2 + +```kotlin +fun encase(baseContext: Context?, vararg hooker: YukiBaseHooker) +``` + +此处的 `baseContext` 只需填入你在 `attachBaseContext` 处得到的 `Context` 即可,其它用法与上述内容完全一致。 + +!> 切勿以 Xposed 方式使用 `encase` 方法而漏掉 `baseContext` 参数,否则你的 Hook 将完全不工作。 \ No newline at end of file diff --git a/docs/config/api-exception.md b/docs/config/api-exception.md new file mode 100644 index 00000000..c5ceacc6 --- /dev/null +++ b/docs/config/api-exception.md @@ -0,0 +1,703 @@ +# API 异常处理 + +> 异常是在开发过程经常遇到的主要问题,这里介绍了 `YukiHookAPI` 在使用过程中可能遇到的常见异常以及处理方式。 + +## 非阻断异常 + +> 这些异常不会导致 APP 停止运行(FC),但是会在控制台打印 `E` 级别的日志,也可能会停止继续执行相关功能。 + +!> `loggerE` You cannot loading a hooker in "onInit" method! Aborted + +异常原因 + +你尝试在继承 `YukiHookXposedInitProxy` 的 Hook 入口类的 `onInit` 方法中装载了 `encase` 方法。 + +> 示例如下 + +```kotlin +class HookEntry : YukiHookXposedInitProxy { + + override fun onInit() { + // ❗错误的使用方法 + YukiHookAPI.encase { + // Your code here. + } + } + + override fun onHook() { + // Your code here. + } +} +``` + +解决方案 + +请在 `onHook` 方法中装载 `encase` 方法。 + +> 示例如下 + +```kotlin +class HookEntry : YukiHookXposedInitProxy { + + override fun onInit() { + // 这里只能装载 configs 方法 + YukiHookAPI.configs { + // Your code here. + } + } + + override fun onHook() { + // ✅ 正确的使用方法 + YukiHookAPI.encase { + // Your code here. + } + } +} +``` + +!> `loggerE` YukiHookAPI try to load HookEntryClass failed + +异常原因 + +`YukiHookAPI` 在尝试装载 Hook 入口类 `onInit` 或 `onHook` 方法时发生了不能处理的异常或找不到入口类。 + +解决方案 + +通常情况下这种错误不会轻易发生,若一旦发生此错误,请自行查看控制台打印的日志定位问题,确定并非自己的代码发生的问题后,可提交日志进行反馈。 + +!> `loggerE` HookClass \[NAME\] not found + +异常原因 + +当前被 Hook 的 `Class` 没有被找到。 + +解决方案 + +请检查目标 `Class` 是否存在,若想忽略此错误请使用 `ignoredHookClassNotFoundFailure` 方法。 + +!> `loggerE` Hook Member \[NAME\] failed + +异常原因 + +Hook 目标方法、构造方法时发生错误。 + +解决方案 + +此问题通常由 Hook Framework 产生,请检查对应的日志内容,若问题持续出现请携带完整日志进行反馈。 + +!> `loggerE` Hooked Member with a finding error by CLASS + +异常原因 + +在 Hook 执行后被 Hook 的 `member` 为 `null` 且已经设置目标 Hook 方法、构造类。 + +解决方案 + +请检查此错误发生前的上一个错误日志,或许在查找方法、构造方法的时候发生了找不到方法、构造方法的错误。 + +!> `loggerE` Hooked Member cannot be non-null by CLASS + +异常原因 + +在 Hook 执行后被 Hook 的 `member` 为 `null` 且没有设置目标 Hook 方法、构造类。 + +> 示例如下 + +```kotlin +injectMember { + // 这里并没有设置需要 Hook 的方法、构造方法的查询条件 + afterHook { + // ... + } +} +``` + +解决方案 + +请确认你已经在 Hook 之前正确设置了要 Hook 的方法、构造方法的查询方式。 + +> 示例如下 + +```kotlin +injectMember { + // ✅ 正确的使用方法举例 + method { + // Your code here. + } + afterHook { + // ... + } +} +``` + +!> `loggerE` No Method name "NAME" matched + +异常原因 + +在使用 `allMethods` 查询需要 Hook 的方法时一个也没有找到。 + +解决方案 + +请确认当前 `Class` 中一定存在一个可以匹配此方法名称的方法。 + +!> `loggerE` No Constructor matched + +异常原因 + +在使用 `allConstructors` 查询需要 Hook 的构造方法时一个也没有找到。 + +解决方案 + +请确认当前 `Class` 是否存在至少一个构造方法。 + +!> `loggerE` Hooked All Members with an error in Class \[NAME\] + +异常原因 + +在 Hook 过程中发生了任意的异常。 + +解决方案 + +这是一个异常汇总提醒,只要 Hook 方法体内发生了异常就会打印此日志,请仔细查看从这里往上的具体异常是什么。 + +!> `loggerE` Try to hook NAME\[NAME\] got an Exception + +异常原因 + +在 Hook 开始时发生了任意的异常。 + +解决方案 + +这是一个 Hook 开始就发生异常的提醒,请仔细查看具体的异常是什么以重新确定问题。 + +!> `loggerE` Method/Constructor/Field match type "TYPE" not allowed + +异常原因 + +在查找方法、构造方法以及变量时设置了不允许的参数类型。 + +> 示例如下 + +```kotlin +// 查询一个方法 +method { + // ❗设置了无效的类型举例 + param(false, 1, 0) + // ❗设置了无效的类型举例 + returnType = false +} + +// 查询一个变量 +field { + // ❗设置了无效的类型举例 + type = false +} +``` + +解决方案 + +在查询中 `param`、`returnType`、`type` 中仅接受 `Class`、`String`、`VariousClass` 类型的传值,不可传入参数实例。 + +> 示例如下 + +```kotlin +// 查询一个方法 +method { + // ✅ 正确的使用方法举例 + param(BooleanType, IntType, IntType) + // ✅ 正确的使用方法举例 + returnType = BooleanType + // ✅ 以下方案也是正确的 + returnType = "java.lang.Boolean" +} + +// 查询一个变量 +field { + // ✅ 正确的使用方法举例 + type = BooleanType +} +``` + +!> `loggerE` NoSuchMethod/NoSuchConstructor/NoSuchField happend in \[NAME\] + +异常原因 + +在查找方法、构造方法以及变量时并未找到目标方法、构造方法以及变量。 + +解决方案 + +请确认你的查询条件是否能正确匹配到目标 `Class` 中的指定方法、构造方法以及变量。 + +!> `loggerE` Trying COUNT times and all failure by RemedyPlan + +异常原因 + +使用 `RemedyPlan` 重新查找方法、构造方法时依然没有找到方法、构造方法。 + +解决方案 + +请确认你设置的 `RemedyPlan` 参数以及宿主内存在的 `Class`,再试一次。 + +!> `loggerE` Try to get field instance failed + +异常原因 + +在使用变量查询结果的 `get` 方法后并没有成功得到对应的实例。 + +> 示例如下 + +```kotlin +field { + // ... +}.get(instance)... +``` + +解决方案 + +请确认当前变量所在的实例是静态的还是动态的,并查看错误日志检查传入的实例类型是否正确。 + +!> `loggerE` You must set a condition when finding a Method/Constructor/Field + +异常原因 + +在查找方法、构造方法以及变量时并未设置任何条件。 + +> 示例如下 + +```kotlin +method { + // 这里没有设置任何条件 +} +``` + +解决方案 + +请将查询条件补充完整并再试一次。 + +!> `loggerE` Can't find this Method/Constructor/Field \[NAME\] because classSet is null + +异常原因 + +在查找方法、构造方法以及变量时所设置的 `Class` 实例为 `null`。 + +> 示例如下 + +```kotlin +// 假设 TargetClass 的实例为 null +TargetClass.method { + // ... +} +``` + +解决方案 + +这种情况比较少见,请检查你要查询的目标 `Class` 是否被正确赋值并检查整个 Hook 流程和使用范围。 + +!> `loggerE` Field match type class is not found + +异常原因 + +在查找变量时所设置的查询条件中 `type` 的 `Class` 实例未被找到。 + +> 示例如下 + +```kotlin +field { + name = "test" + // 假设这里设置的 type 的 Class 并不存在 + type = "com.example.TestClass" +} +``` + +解决方案 + +请检查查询条件中 `type` 的 `Class` 是否存在,然后再试一次。 + +!> `loggerE` Method match returnType class is not found + +异常原因 + +在查找方法时所设置的查询条件中 `returnType` 的 `Class` 实例未被找到。 + +> 示例如下 + +```kotlin +method { + name = "test" + // 假设这里设置的 returnType 的 Class 并不存在 + returnType = "com.example.TestClass" +} +``` + +解决方案 + +请检查查询条件中 `returnType` 的 `Class` 是否存在,然后再试一次。 + +!> `loggerE` Method/Constructor match paramType\[INDEX\] class is not found + +异常原因 + +在查找方法、构造方法时所设置的查询条件中 `param` 的 `index` 号下标的 `Class` 实例未被找到。 + +```kotlin +method { + name = "test" + // 假设这里设置的 1 号下标的 Class 并不存在 + param(StringType, "com.example.TestClass", BooleanType) +} +``` + +解决方案 + +请检查查询条件中 `param` 的 `index` 号下标的 `Class` 是否存在,然后再试一次。 + +## 阻断异常 + +> 这些异常会直接导致 APP 停止运行(FC),同时会在控制台打印 `E` 级别的日志,还会造成 Hook 进程“死掉”。 + +!> `IllegalStateException` YukiHookModulePrefs not allowed in Custom Hook API + +异常原因 + +在 Hook 自身 APP(非 Xposed 模块) 中使用了 `YukiHookModulePrefs`。 + +> 示例如下 + +```kotlin +class MyApplication : Application() { + + override fun attachBaseContext(base: Context?) { + YukiHookAPI.encase(base) { + // ❗不能在这种情况下使用 prefs + prefs.getBoolean("test_data") + } + super.attachBaseContext(base) + } +} +``` + +解决方案 + +你只能在 [作为 Xposed 模块使用](config/xposed-using) 时使用 `YukiHookModulePrefs`,在 Hook 自身 APP 中请使用原生的 `Sp` 存储。 + +!> `IllegalStateException` Xposed modulePackageName load failed, please reset and rebuild it + +异常原因 + +在 Hook 过程中使用 `YukiHookModulePrefs` 时无法读取装载时的 `modulePackageName` 导致不能确定自身模块的包名。 + +解决方案 + +请仔细阅读 [这里](config/xposed-using?id=modulepackagename-参数) 的帮助文档,正确配置模块的 Hook 入口类包名。 + +!> `IllegalStateException` If you want to use module prefs, you must set the context instance first + +异常原因 + +在模块中使用了 `YukiHookModulePrefs` 存储数据但并未传入 `Context` 实例。 + +> 示例如下 + +```kotlin +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // ❗错误的使用方法 + YukiHookModulePrefs().getBoolean("test_data") + } +} +``` + +解决方案 + +在 `Activity` 中推荐使用 `modulePrefs` 方法来装载 `YukiHookModulePrefs`。 + +> 示例如下 + +```kotlin +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 可以使用但是不推荐 + YukiHookModulePrefs(this).getBoolean("test_data") + // ✅ 推荐的使用方法 + modulePrefs.getBoolean("test_data") + } +} +``` + +!> `IllegalStateException` Key-Value type TYPE is not allowed + +异常原因 + +在使用 `YukiHookModulePrefs` 的 `get` 或 `put` 方法时传入了不支持的存储类型。 + +解决方案 + +`YukiHookModulePrefs` 支持的类型只有 `String`、`Int`、`Float`、`Long`、`Boolean`,请传入支持的类型。 + +!> `IllegalStateException` HookParam args is empty + +异常原因 + +在 `HookParam` 中调用 `firstArgs` 或 `lastArgs` 但原始 `param` 数组为空。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 调用了此变量 + firstArgs... + // 调用了此变量 + lastArgs... + } +} +``` + +解决方案 + +请确认你 Hook 的目标方法、构造方法的方法参数数量是否不为空,否则你无法使用此功能。 + +!> `IllegalStateException` HookParam instance got null! Is this a static member? + +异常原因 + +在 `HookParam` 中调用 `instance` 变量或 `instance` 方法但获取不到当前实例的对象。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 调用了此变量 + instance... + // 调用了此方法 + instance()... + } +} +``` + +解决方案 + +请确认你 Hook 的方法是否为静态类型,静态类型的方法没有实例,不能使用此功能,若非静态方法,请检查实例是否已经销毁。 + +!> `IllegalStateException` Current hook Method type is wrong or null + +异常原因 + +在 `HookParam` 中调用 `method` 变量但获取不到当前实例的方法实例。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 调用了此变量 + method... + } +} +``` + +解决方案 + +请确认你 Hook 的方法是构造方法还是普通方法并使用对应类型的方法获取指定的实例。 + +!> `IllegalStateException` Current hook Constructor type is wrong or null + +异常原因 + +在 `HookParam` 中调用 `constructor` 变量但获取不到当前实例的方法实例。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 调用了此变量 + constructor... + } +} +``` + +解决方案 + +请确认你 Hook 的方法是普通方法还是构造方法并使用对应类型的方法获取指定的实例。 + +!> `IllegalStateException` HookParam instance cannot cast to TYPE + +异常原因 + +在 `HookParam` 中调用 `instance` 方法指定了错误的类型。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 类型被 cast 为 Activity 但假设当前实例的类型并非此类型 + instance()... + } +} +``` + +解决方案 + +请确认当前 Hook 实例的正确类型并重新填写泛型中的类型,若不能确定请使用 `Any` 或直接使用 `instance` 变量。 + +!> `IllegalStateException` HookParam Method args is empty, mabe not has args + +异常原因 + +在 `HookParam` 中调用 `ArgsModifyer.set` 方法但是当前实例的方法参数数组为空。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 调用了此方法 + args(...).set(...) + } +} +``` + +解决方案 + +请确认你 Hook 的目标方法、构造方法的方法参数数量是否不为空,否则你无法使用此功能。 + +!> `IllegalStateException` HookParam Method args index out of bounds, max is NUMBER + +异常原因 + +在 `HookParam` 中调用 `ArgsModifyer.set` 方法指定了超出方法参数下标的数组序号。 + +> 示例如下 + +```kotlin +injectMember { + // ... + afterHook { + // 下标从 0 开始,假设原始的参数下标是 5 个,但是这里填写了 6 + args(index = 6).set(...) + } +} +``` + +解决方案 + +请确认你 Hook 的目标方法、构造方法的方法参数个数,并重新设置数组下标。 + +!> `IllegalStateException` PackageParam got null ClassLoader + +异常原因 + +在 `PackageParam` 中调用了 `appClassLoader` 变量但是无法获取到实例对象。 + +> 示例如下 + +```kotlin +encase { + // 调用了此变量 + appClassLoader... +} +``` + +解决方案 + +这种情况几乎不存在,除非模块被装载的宿主或目标 Xposed 框架自身存在问题,若真的发生了此问题,请携带详细日志进行反馈。 + +!> `IllegalStateException` PackageParam got null appContext + +异常原因 + +在 `PackageParam` 中调用了 `appContext` 变量但是无法获取到实例对象。 + +> 示例如下 + +```kotlin +encase { + // 调用了此变量 + appContext... +} +``` + +解决方案 + +这种情况几乎不存在,除非 Android 系统结构损坏或 Xposed 框架自身存在问题,若真的发生了此问题,请携带详细日志进行反馈。 + +!> `IllegalStateException` VariousClass match failed of those CLASSES + +异常原因 + +在使用 `VariousClass` 创建不确定的 `Class` 对象时全部的 `Class` 都没有被找到。 + +解决方案 + +检查当前 Hook 的宿主内是否存在其中能够匹配的 `Class` 后,再试一次。 + +!> `IllegalStateException` Cannot get hook class "NAME" cause THROWABLE + +异常原因 + +在 `hook` 方法体非 `onPrepareHook` 方法内调用了 `instanceClass` 变量且当前 Hook 的 `Class` 不存在。 + +> 示例如下 + +```kotlin +TargetClass.hook { + // 可能的情况为在非 onPrepareHook 方法体内调用了 instanceClass 变量用于打印日志 + loggerD(msg = "$instanceClass hook start") +} +``` + +解决方案 + +在 `hook` 内直接使用 `instanceClass` 是很危险的,若 Class 不存在则会直接导致 Hook 进程“死掉”。 + +详情请参考 [状态监听](guide/example?id=状态监听)。 + +!> `IllegalStateException` Hook Members is empty, hook aborted + +异常原因 + +使用了 `hook` 方法体但其中并没有填写内容。 + +> 示例如下 + +```kotlin +TargetClass.hook { + // 这里没有填写任何内容 +} +``` + +解决方案 + +你必须在 `hook` 方法体内加入至少一个 `injectMember` 方法。 + +!> `IllegalStateException` paramTypes is empty, please delete param() method + +异常原因 + +在查找方法、构造方法时保留了空的 `param` 方法。 + +> 示例如下 + +```kotlin +method { + // 没有填写任何参数 + param() +} +``` + +解决方案 + +若要标识此方法、构造方法没有参数,你可以什么都不写或设置 `paramCount = 0` 即可。 \ No newline at end of file diff --git a/docs/config/api-using.md b/docs/config/api-using.md new file mode 100644 index 00000000..aa9c4ec6 --- /dev/null +++ b/docs/config/api-using.md @@ -0,0 +1,108 @@ +# 作为 Hook API 使用的相关配置 + +> 作为 Hook API 通常为做自身 APP 热更新或功能需要以及产品测试的 Hook 操作。 + +## 依赖配置 + +你只需要集成 `com.highcapable.yukihookapi:api` 依赖即可。 + +然后请集成你目标使用的 `Hook Framework` 依赖。 + +## 入口配置 + +创建你的自定义 `Application`。 + +在 `attachBaseContext` 中添加 `YukiHookAPI.encase` 方法。 + +> 示例如下 + +```kotlin +class MyApplication : Application() { + + override fun attachBaseContext(base: Context?) { + // 装载 Hook Framework + // + // Your code here. + // + // 配置 YukiHookAPI + YukiHookApi.configs { + // Your code here. + } + // 装载 YukiHookAPI + YukiHookAPI.encase(base) { + // Your code here. + } + super.attachBaseContext(base) + } +} +``` + +这样,你就完成了 API 的相关配置。 + +你可以 [点击这里](config/api-example?id=作为-hook-api-使用需要注意的地方) 查看异同点和注意事项。 + +!> 你不能再使用 `loadApp` 进行包装,可直接开始编写你的 Hook 代码。 + +## Hook Framework + +> 这里给出了一些较高使用率的 `Hook Framework` 如何对接 `YukiHookAPI` 的相关方式。 + +### [Pine](https://github.com/canyie/pine) + +> 所需 Xposed API 依赖 `top.canyie.pine:xposed` + +> 示例如下 + +```kotlin +override fun attachBaseContext(base: Context?) { + // 装载 Pine + PineConfig.debug = true + PineConfig.debuggable = BuildConfig.DEBUG + // 装载 YukiHookAPI + YukiHookAPI.encase(base) { + // Your code here. + } + super.attachBaseContext(base) +} +``` + +### [SandHook](https://github.com/asLody/SandHook) + +> 所需 Xposed API 依赖 `com.swift.sandhook:xposedcompat` 或 `com.swift.sandhook:xposedcompat_new` + +> 示例如下 + +```kotlin +override fun attachBaseContext(base: Context?) { + // 装载 SandHook + SandHookConfig.DEBUG = BuildConfig.DEBUG + XposedCompat.cacheDir = base?.cacheDir + XposedCompat.context = base + XposedCompat.classLoader = javaClass.classLoader + XposedCompat.isFirstApplication = base?.processName == base?.packageName + // 装载 YukiHookAPI + YukiHookAPI.encase(base) { + // Your code here. + } + super.attachBaseContext(base) +} +``` + +### [Whale](https://github.com/asLody/whale) + +> 所需 Xposed API 依赖 `com.wind.xposed:xposed-on-whale` + +请参考 [xposed-hook-based-on-whale](https://github.com/WindySha/xposed-hook-based-on-whale)。 + +> 示例如下 + +```kotlin +override fun attachBaseContext(base: Context?) { + // 装载 Whale 不需要任何配置 + // 装载 YukiHookAPI + YukiHookAPI.encase(base) { + // Your code here. + } + super.attachBaseContext(base) +} +``` \ No newline at end of file diff --git a/docs/config/r8-proguard.md b/docs/config/r8-proguard.md new file mode 100644 index 00000000..561f6dfc --- /dev/null +++ b/docs/config/r8-proguard.md @@ -0,0 +1,28 @@ +# R8 与 Proguard 混淆 + +> 大部分场景下 Xposed 模块可通过原生混淆压缩体积,这里介绍了混淆的配置方法。 + +## R8 + +> 如果你使用的是 `R8`,那么你无需对 `YukiHookAPI` 进行任何特殊配置。 + +## Proguard + +> 如果你仍然在使用 `Proguard`,你需要做一些规则配置。 + +在 `proguard-rules.pro` 添加如下代码即可。 + +> 示例如下 + +```proguard +-keep class com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus {*;} +-keep class 这里填你的 HookEntryClass 入口类完整包名_YukiHookXposedInit {*;} +``` + +!> 自从 Android Gradle Plugin 4.2 后,拥有 Android Jetpack 套件最新版本的混淆处理程序默认均为 `R8`,基本可以不需要考虑混淆的问题。 + +若要在任何版本下启用 `R8`,请在 `gradle.properties` 文件中加入如下规则,Android Gradle Plugin 7.0 及以上版本无需任何配置。 + +```gradle +android.enableR8=true +``` \ No newline at end of file diff --git a/docs/config/xposed-using.md b/docs/config/xposed-using.md new file mode 100644 index 00000000..2411b235 --- /dev/null +++ b/docs/config/xposed-using.md @@ -0,0 +1,96 @@ +# 作为 Xposed 模块使用的相关配置 + +> 这里介绍了 `YukiHookAPI` 作为 Xposed 模块使用的相关配置方法。 + +## 依赖配置 + +> 作为 Xposed 模块,`YukiHookAPI` 提供了一个自动处理程序。 + +你需要在你的 `build.gradle` 中集成 `com.highcapable.yukihookapi:ksp-xposed` 依赖的最新版本。 + +## 自定义处理程序 + +> 你可以对 `YukiHookAPI` 将如何生成 `xposed_init` 入口进行相关配置。 + +### InjectYukiHookWithXposed 注释 + +```kotlin +annotation class InjectYukiHookWithXposed(val sourcePath: String, val modulePackageName: String) +``` + +`InjectYukiHookWithXposed` 注释是一个标记模块 Hook 入口的重要注释。 + +!> `InjectYukiHookWithXposed` 注释的 `Class` 必须实现 `YukiHookXposedInitProxy` 接口。 + +!> 在你当前项目中的所有 `Class` 标记中只能存在一次,若存在多个声明自动处理程序会在编译时抛出异常,你可以自定义其相关参数。 + +#### sourcePath 参数 + +`sourcePath` 参数决定了自动处理程序自动查找并匹配你当前项目路径的重要标识,此参数的内容为相对路径匹配,默认参数为 `src/main`。 + +!> 如果你的项目不在 `...app/src/main...` 或你手动使用 `sourceSets` 设置了项目路径,你就需要手动设置 `sourcePath` 参数,否则自动处理程序将无法识别你的项目路径并会在编译时抛出异常。 + +> 示例如下 + +```kotlin +@InjectYukiHookWithXposed(sourcePath = "src/custom") +``` + +`sourcePath` 使用的文件路径分隔符写法根据 `Windows` 和 `Unix` 将自动进行识别,使用 `/` 或 `\` 均可。 + +#### modulePackageName 参数 + +`modulePackageName` 是你当前项目的包名,也就是你的模块包名,默认留空自动处理程序将自动根据你注释的 `Class` 入口类的路径进行生成。 + +!> 若你想使用包名自动生成,你的 Hook 入口类 `HookEntryClass` 就要遵守包名的命名规范,格式为 `包名.hook.HookEntryClass` 或 `包名.hook.子包名.HookEntryClass`。 + +示例模块包名 `com.example.demo` + +示例 1 `com.example.demo.hook.MainHook` + +示例 2 `com.example.demo.hook.custom.CustomClass` + +若你不想使用此格式定义入口类的包名,你可以直接设置 `modulePackageName` 的参数。 + +> 示例如下 + +```kotlin +@InjectYukiHookWithXposed(modulePackageName = "com.example.demo") +``` + +你也可以直接设置为你的 `BuildConfig.APPLICATION_ID`。 + +> 示例如下 + +```kotlin +@InjectYukiHookWithXposed(modulePackageName = BuildConfig.APPLICATION_ID) +``` + +!> 只要你自定义了 `modulePackageName` 的参数,你就会在编译时收到警告。 + +> 示例如下 + +``` +You set the customize module package name to "com.example.demo", please check for yourself if it is correct +``` + +### YukiHookXposedInitProxy 接口 + +```kotlin +interface YukiHookXposedInitProxy { + + fun onInit() + + fun onHook() +} +``` + +`YukiHookXposedInitProxy` 接口为你的 `HookEntryClass` 必须实现的接口,这是你的模块开始 Hook 的起点。 + +若要了解更多可 [点击这里](api/document?id=yukihookxposedinitproxy-interface) 进行查看。 + +当你的模块被 Xposed 装载后,`onHook` 方法将会被回调,你需要在此方法中开始使用 `YukiHookAPI`。 + +> 基本的调用流程为 `_YukiHookXposedInit.handleLoadPackage` → `HookEntryClass.onInit` → `HookEntryClass.onHook` → `YukiHookAPI.onXposedLoaded` + +详情请参考 [API 基本配置](config/api-example)。 \ No newline at end of file diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f17b73bbbaf824ecac9f1d894e0baeae68fd9eda GIT binary patch literal 9662 zcmd^_TTfJ16vwym!N6ZEHWX25DUlPSR9jM#{|l& z*JB%z5|P&;q2`^ce=?9IAU&jBoXD8SRQ-m#oJ?`Ljs2xk&iM*Am)-HwEZAeCfL+%$ z)~JJU)8C!wGdiz1C2NeXgFG`n%ypdpUP6BAA>xE}#;(1{O!jmUyKS9PpX}TxoWf(5 zgY5lI;7=(Z$Ld1^NImk(jt*q!er=~sNb0UB9)l~V8|#j~vD3dJ*lE8n3P<6}_1IyL zOkE56CLA2ZsssIzVY1i)?d4A{8%L^zj!y9N z#nba}i&l45?CxP|H16$XUik@SMyCvjy-h2uux(gh*=ASF$ zH1O+f;Ao2`0lx^NIhNAI-WAPSd1<4j znMNW9hC3lGh<|@f@LyU-F-92Y$Qb%g;H{ z%f2-D%~xW3&Kb)nDH6Yl1yAA18^(&9KE&?n#o{f^Sd`I(g_|3(Fug(227J1$)mz7G zr+0bZRt=oF2NVmBxgXk>Wz^AJmM(tBGKx_*z20JBjo&}$-FuVE{*ifjfyAGmA*uLp zpT6`q(@D2i12vt%@AbZnm94j)<@AkODO+!`mr*~qHS@168OJA^nu2a?Gauh~2>+#SEhf$7r#pa)53KpCL_fAQ z^PBq#<$K{R;A*eNL7z5`byj@mN{=-^^<&#e@iQ*3do+d{4}E>8R(zY~R{lf%*e+81 z^xs>LfbTB>)v}K8IkR?h4(K~KCC`eVvCB4*;NN=@sPEIb=`&g9wRhTF-%l@#Z{K-r z&3{4kV;>s~dxO#cLiNnOpMKpq0DM<7Y40jo51S+}@4A*p-)`u)=4b4(ez^G=v#mqg z`faok_k!j@&E^}chn~~q&zIsH?HXXwN&uPILGnoBw z&+Pouns1ZXyG?YvKLWUAZwTht@7fn*wEGFrF81ZzTlL4YS;$HplR5V?2h>Ta2o9NRkGJiv#k?+o$S&6`*~E~U%b78 ziQ4-k!yQu8-rv2|f%n! 这里介绍了 `YukiHookAPI` 的基本工作方式以及列举了简单的 Hook 例子和常用功能。 + +## 结构图解 + +> 下方的结构描述了 `YukiHookAPI` 的基本工作方式和原理。 + +``` +Host Environment +└── YukiHookCreater + └── Class + └── MemberHookCreater + └── Member + ├── Before + └── After + MemberHookCreater + └── Member + ├── Before + └── After + ... +``` + +> 上方的结构换做代码将可写为如下形式。 + +```kotlin +TargetClass.hook { + injectMember { + method { + // Your code here. + } + beforeHook { + // Your code here. + } + afterHook { + // Your code here. + } + } +} +``` + +## Demo + +> 你可以在下方找到 API 提供的 Demo 来学习 `YukiHookAPI` 的使用方法。 + +- 宿主 APP Demo [点击这里查看](https://github.com/fankes/YukiHookAPI/tree/master/demo-app) + +- 模块 APP Demo [点击这里查看](https://github.com/fankes/YukiHookAPI/tree/master/demo-module) + +同时安装宿主和模块 Demo,通过激活模块来测试宿主中被 Hook 的功能。 + +## 一个简单的 Hook 例子 + +假设,我们要 Hook `com.android.browser` 中的 `onCreate` 方法并弹出一个对话框。 + +在 `encase` 方法体中添加代码。 + +> 示例如下 + +```kotlin +loadApp(name = "com.android.browser") { + ActivityClass.hook { + injectMember { + method { + name = "onCreate" + param(BundleClass) + returnType = UnitType + } + afterHook { + AlertDialog.Builder(instance()) + .setTitle("Hooked") + .setMessage("I am hook!") + .setPositiveButton("OK", null) + .show() + } + } + } +} +``` + +至此,`onCreate` 方法将被成功 Hook 并在 `com.android.browser` 中的每个 `Activity` 启动时弹出此对话框。 + +那么,我想继续 Hook `onStart` 方法要怎么做呢? + +在刚刚的代码中,继续插入一个 `injectMember` 方法体即可。 + +> 示例如下 + +```kotlin +loadApp(name = "com.android.browser") { + ActivityClass.hook { + injectMember { + method { + name = "onCreate" + param(BundleClass) + returnType = UnitType + } + afterHook { + AlertDialog.Builder(instance()) + .setTitle("Hooked") + .setMessage("I am hook!") + .setPositiveButton("OK", null) + .show() + } + } + injectMember { + method { + name = "onStart" + returnType = UnitType + } + afterHook { + // Your code here. + } + } + } +} +``` + +对于当前项目下没有的 `Class`,你可以使用 `stub` 方式或 `findClass` 方法来得到需要 Hook 的类。 + +比如,我要得到 `com.example.demo.TestClass`。 + +> 示例如下 + +```kotlin +findClass(name = "com.example.demo.TestClass").hook { + injectMember { + // Your code here. + } +} +``` + +若 `com.example.demo` 是你要 Hook 的 APP,那么写法可以更简单。 + +> 示例如下 + +```kotlin +findClass(name = "$packageName.TestClass").hook { + injectMember { + // Your code here. + } +} +``` + +到这里有些同学可能就开始说了,在某些场景下 `findClass` 显得有些繁琐。 + +因为可能有些同学有如下需求。 + +> 示例如下 + +```kotlin +const val TestClass = "com.example.demo.TestClass" + +TestClass.hook { + injectMember { + // Your code here. + } +} +``` + +没关系,你还可以使用字符串类名直接创建一个 Hook。 + +> 示例如下 + +```kotlin +("$packageName.TestClass").hook { + injectMember { + // Your code here. + } +} +``` + +## 异常处理 + +> `YukiHookAPI` 重新设计了对异常的监听,任何异常都不会在 Hook 过程中抛出,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。 + +你可以处理 Hook 方法过程发生的异常。 + +> 示例如下 + +```kotlin +injectMember { + // Your code here. +}.result { + // 处理 Hook 开始时的异常 + onHookingFailure {} + // 处理 Hook 过程中的异常 + onConductFailure { param, throwable -> } + // 处理全部异常 + onAllFailure {} + // ... +} +``` + +你还可以处理 Hook 的 `Class` 不存在时发生的异常。 + +> 示例如下 + +```kotlin +TargetClass.hook { + injectMember { + // Your code here. + } +}.onHookClassNotFoundFailure { + // Your code here. +} +``` + +你还可以处理查找方法时的异常。 + +> 示例如下 + +```kotlin +method { + // Your code here. +}.onNoSuchMethod { + // Your code here. +} +``` + +这里介绍了可能发生的常见异常,若要了解更多请参考 [API 异常处理](config/api-exception.md)。 + +## 状态监听 + +在使用 `XposedHelper` 的同学往往会在 Hook 后打印 `UnHook` 的方法确定是否 Hook 成功。 + +在 `YukiHookAPI` 中,你可以用以下方法方便地重新实现这个功能。 + +首先我们可以监听 Hook 已经准备开始。 + +> 示例如下 + +```kotlin +YourClass.hook { + // Your code here. +}.onPrepareHook { + loggerD(msg = "$instanceClass hook start") +} +``` + +!> 请注意 `instanceClass` 建议只在 `onPrepareHook` 中使用,万一被 Hook 的 `Class` 不存在将会抛出无法拦截的异常导致 Hook 进程“死掉”。 + +然后,我们还可以对 Hook 的方法结果进行监听是否成功。 + +> 示例如下 + +```kotlin +injectMember { + // Your code here. +}.onHooked { member -> + loggerD(msg = "$member has hooked") +} +``` + +## 扩展用法 + +> 你可以在 Hook 过程中使用下面的方法方便地实现各种判断和功能。 + +### 多个宿主 + +如果你的模块需要同时处理多个 APP 的 Hook 事件,你可以使用 `loadApp` 方法体来区分你要 Hook 的 APP。 + +> 示例如下 + +```kotlin +loadApp(name = "com.android.browser") { + // Your code here. +} +loadApp(name = "com.android.phone") { + // Your code here. +} +``` + +详细用法可 [点击这里](api/document?id=loadapp-method) 进行查看。 + +### 多个进程 + +如果你 Hook 的宿主 APP 有多个进程,你可以使用 `withProcess` 方法体来对它们分别进行 Hook。 + +> 示例如下 + +```kotlin +withProcess(mainProcessName) { + // Your code here. +} +withProcess(name = "$packageName:tool") { + // Your code here. +} +``` + +详细用法可 [点击这里](api/document?id=withprocess-method) 进行查看。 + +## 写法优化 + +为了使代码更加简洁,你可以删去 `YukiHookAPI` 的名称,将你的 `onHook` 入口写作 `lambda` 形式。 + +> 示例如下 + +```kotlin +override fun onHook() = encase { + // Your code here. +} +``` + +## Xposed 模块判断自身激活状态 + +通常情况下,我们会选择写一个方法,使其返回 `false`,然后 Hook 掉这个方法使其返回 `true` 来证明 Hook 已经生效。 + +在 `YukiHookAPI` 中你完全不需要再这么做了,`YukiHookAPI` 已经帮你封装好了这个操作,你可以直接进行使用。 + +现在,你可以直接使用 `isXposedModuleActive` 在模块中判断自身是否被激活。 + +> 示例如下 + +```kotlin +if(isXposedModuleActive) { + // Your code here. +} +``` + +由于一些特殊原因,在太极、无极中的模块无法使用标准方法检测激活状态。 + +此时你可以在 `Activity` 中使用 `isTaiChiModuleActive` 判断自身是否被激活。 + +> 示例如下 + +```kotlin +if(isTaiChiModuleActive) { + // Your code here. +} +``` + +若你想使用两者得兼的判断方案,`YukiHookAPI` 同样为你封装了便捷的方式。 + +此时你可以在 `Activity` 中使用 `isModuleActive` 判断自身是否在 Xposed 或太极、无极中被激活。 + +> 示例如下 + +```kotlin +if(isModuleActive) { + // Your code here. +} +``` + +若要了解更多可 [点击这里](api/document?id=ismoduleactive-field) 进行查看。 + +!> 除了提供标准 API 的 Hook 框架之外,其它情况下模块可能都将无法判断自己是否被激活。 \ No newline at end of file diff --git a/docs/guide/home.md b/docs/guide/home.md new file mode 100644 index 00000000..34644599 --- /dev/null +++ b/docs/guide/home.md @@ -0,0 +1,142 @@ +# 介绍 + +> 这是一个 Hook API 框架,本身不提供任何 Hook 功能,需要 Xposed 基础 API 的支持。 + +## 背景 + +这是一个使用 `Kotlin` 重新构建的高效 Xposed Hook API。 + +名称取自 [《ももくり》女主 栗原 雪(Yuki)](https://www.bilibili.com/bangumi/play/ss5016)。 + +前身为 [开发学习项目](https://github.com/fankes/TMore) 中使用的 Innocent Xposed API,现在重新命名并开源。 + +## 用途 + +`YukiHookAPI` 完全采用 `Kotlin` `lambda` 语法构建。 + +抛弃原始不太友好的 `XposedHelper`,你可以使用它来轻松创建 Xposed 模块以及轻松实现自定义 Hook API。 + +## 语言要求 + +请使用 `Kotlin`,框架部分代码构成同样兼容 `Java` 但基础 Hook 场景的实现可能完全无法使用。 + +文档全部的 Demo 示例代码都将使用 `Kotlin` 进行描述,如果你完全不会使用 `Kotlin` 那你将有可能无法使用 `YukiHookAPI`。 + +## 功能特性 + +- Xposed 模块开发 + + 自动构建程序可以帮你快速创建一个 Xposed 模块,完全省去配置入口类和 `xposed_init` 文件。 + +- 轻量优雅 + + 拥有一套强大、优雅和人性化的 `Kotlin Lambda Hook API`,可以帮你快速实现 `Method`、`Constructor`、`Field` 的查找以及 Hook。 + +- 高效调试 + + 拥有丰富的调试日志功能,细到每个 Hook 方法的名称、所在类以及查找耗时,可进行快速调试和排错。 + +- 方便移植 + + 原生支持 Xposed API 用法,并原生对接 Xposed API,拥有 Xposed API 的 Hook 框架都能快速对接 Yuki Hook API。 + +- 支持混淆 + + 使用 `YukiHookAPI` 构建的 Xposed 模块原生支持 R8 压缩优化混淆,混淆不会破坏 Hook 入口点,R8 下无需任何其它配置。 + +- 快速上手 + + 简单易用,不需要繁琐的配置,不需要十足的开发经验,搭建环境集成依赖即可立即开始使用。 + +## 灵感来源 + +以前,我们在构建 Xposed 模块的时候,首先需要在 `assets` 下创建 `xposed_init` 文件。 + +然后,将自己的入口类名手动填入文件中,使用 `XposedHelper` 去实现我们的 Hook 逻辑。 + +自 `Kotlin` 作为 Android 主要开发语言以来,这套 API 用起来确实已经不是很优雅了。 + +有没有什么 好用、轻量、优雅 的解决办法呢? + +本着这样的想法,`YukiHookAPI` 诞生了。 + +现在,我们只需要编写少量的代码,一切时间开销和花费交给自动化处理。 + +> 示例如下 + + + +#### **Yuki Hook API** + +```kotlin +@InjectYukiHookWithXposed +class MainHook : YukiHookXposedInitProxy { + + override fun onHook() = encase { + loadApp(name = "com.android.browser") { + ActivityClass.hook { + injectMember { + method { + name = "onCreate" + param(BundleClass) + } + beforeHook { + // Your code here. + } + afterHook { + // Your code here. + } + } + } + } + } +} +``` + +#### **Xposed API** + +```kotlin +class MainHook : IXposedHookLoadPackage { + + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { + if (lpparam.packageName == "com.android.browser") + XposedHelpers.findAndHookMethod( + Activity::class.java.name, + lpparam.classLoader, + "onCreate", + Bundle::class.java, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam?) { + // Your code here. + } + + override fun afterHookedMethod(param: MethodHookParam?) { + // Your code here. + } + }) + } +} +``` + + + +是的,你没有看错,仅仅就需要这几行代码,就一切安排妥当。 + +代码量少,逻辑清晰,借助高效强大的 `YukiHookAPI`,你就可以实现一个非常简单的 Xposed 模块。 + +## 支持的 Hook 框架 + +以下是 `YukiHookAPI` 支持的 `Hook Framework` 以及 Xposed 框架。 + +| Hook Framework | ST | Describe | +| --------------------------------------------------------- | --- | ----------------------------------------------------------------------------------------- | +| [LSPosed](https://github.com/LSPosed/LSPosed) | ✅ | 多场景下稳定使用 | +| [EdXposed](https://github.com/ElderDrivers/EdXposed) | ✅ | 部分兼容 | +| [Pine](https://github.com/canyie/pine) | ⭕ | 可以使用 | +| [SandHook](https://github.com/asLody/SandHook) | ⭕ | 可以使用 | +| [Whale](https://github.com/asLody/whale) | ⭕ | 需要 [xposed-hook-based-on-whale](https://github.com/WindySha/xposed-hook-based-on-whale) | +| [YAHFA](https://github.com/PAGalaxyLab/YAHFA) | ❗ | 需要自行实现 Xposed API | +| [FastHook](https://github.com/turing-technician/FastHook) | ❗ | 需要自行实现 Xposed API | +| [Epic](https://github.com/tiann/epic) | ❗ | 需要自行对接 [Dexposed](https://github.com/alibaba/dexposed) | +| [TaiChi](https://github.com/taichi-framework/TaiChi) | ⭕ | 可以作为模块使用 | +| [Xposed](https://github.com/rovo89/Xposed) | ❎ | 未测试,不再推荐使用 | \ No newline at end of file diff --git a/docs/guide/knowledge.md b/docs/guide/knowledge.md new file mode 100644 index 00000000..6a48d722 --- /dev/null +++ b/docs/guide/knowledge.md @@ -0,0 +1,63 @@ +# 基础知识 + +> 这里介绍了 Xposed 以及 Hook 的工作原理,已经了解的同学可以略过。 + +## Xposed 是什么 + +> Xposed 框架(Xposed Framework)是一套开源的、在 Android 高权限模式下运行的框架服务,可以在不修改 APK 文件的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。 + +上述内容复制自百度百科。 + +## Xposed 能做什么 + +> 下方的结构描述了 Xposed 的基本工作方式和原理。 + +``` +Xposed Framework +└── App's Environment + └── Hooker (Hooked) + ... + App's Environment + └── Hooker (Hooked) + ... + ... +``` + +我们可以在宿主(APP)运行时通过注入宿主(APP)来达到控制其行为的最终目的。 + +Xposed 的这种运行方式被称为寄生,Xposed 模块跟随宿主的生命周期,在宿主的生命周期内完成自己的生命历程。 + +我们可以通过反射的方式调用宿主的方法、变量、构造方法,以及使用 `XposedBridge` 所提供的 Hook 操作动态地在宿主(APP)要执行的方法前后插入自己的代码,或完全替换目标,甚至是拦截。 + +## 发展过程 + +如今的 Xposed 管理器已完全被其衍生作品替代,而 SuperSU 的时代也已经落幕了,现在,借助 Magisk 使后面的一切又成为了可能。 + +> 其发展史大致可分为 Xposed(Dalvik)Xposed(ART)Xposed(Magisk)EdXposed(Riru)/LSPosed(Riru/Zygisk) + +## 衍生产品 + +> 下方的结构描述了类似 Xposed 的 Hook Framework 的工作方式和原理。 + +``` +App's Environment +└── Hook Framework + └── Hooker (Hooked) + ... +``` + +通过 Xposed 的运行原理,从而衍生了很多同类型框架,随着当今时代的移动设备获取 Root 权限甚至刷机越来越困难且不是刚需的时候,一些免 Root 框架也随之产生,例如太极。 + +这些在 ART 层面上的 Hook 框架同样也可不借助 Xposed API 完成其和 Xposed 原理一样的 Hook 流程,免 Root 的运行原理为修改 APK 并将 Hook 进程注入宿主,通过外部模块对其进行控制。 + +另外一种产品就是利用 Android 运行环境现有的功能虚拟出一个完全与当前设备系统一样的环境,并在其中运行 APP,这个就是虚拟 APP 技术 VirtualApp,后来衍生为 VirtualXposed。 + +上述提到的免 Root 框架分别为太极/无极VirtualXposed/SandVXposed。 + +## YukiHookAPI 做了什么 + +自从 Xposed 出现到现在为止,除了开发者人人皆知的 `XposedHelper`,依然没有一套针对 `Kotlin` 打造的语法糖以及用法封装十分完善的 API。 + +本 API 框架的诞生就是希望在 Xposed 的如今时代,能让更多有动手能力的 Xposed 模块开发者少走弯路,更容易、更简单地完成整个开发流程。 + +未来,`YukiHookAPI` 将在使用 Xposed API 的目标基础上适配更多第三方 Hook 框架,使得整个生态得到完善,并帮助更多开发者让 Xposed 模块开发变得更加简单和易懂。 \ No newline at end of file diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md new file mode 100644 index 00000000..a3d79754 --- /dev/null +++ b/docs/guide/quick-start.md @@ -0,0 +1,145 @@ +# 快速开始 + +> 集成 `YukiHookAPI` 到你的项目中。 + +## 环境要求 + +- Windows 7 及以上/macOS 10.14 及以上/Linux 发行版(Arch/Debian) + +- Android Studio 4.1 及以上 + +- IntelliJ IDEA 2021.01 及以上 + +- Kotlin 1.6.0 及以上 + +- Android Gradle Plugin 7.0 及以上 + +- Gradle 7.0 及以上 + +## 集成依赖 + +在你的项目 `build.gradle` 中添加依赖。 + +> 示例如下 + +```gradle +repositories { + google() + mavenCentral() + // ❗若你的 Plugin 版本过低,作为 Xposed 模块使用务必添加,其它情况可选 + maven { url "https://dl.bintray.com/kotlin/kotlin-eap" } + // ❗作为 Xposed 模块使用务必添加,其它情况可选 + maven { url "https://api.xposed.info/" } + // MavenCentral 有 2 小时缓存,若无法集成最新版本请添加此地址 + maven { url "https://s01.oss.sonatype.org/content/repositories/releases" } +} +``` + +在你的 app `build.gradle` 中添加 `plugin`。 + +> 示例如下 + +```gradle +plugins { + // ❗作为 Xposed 模块使用务必添加,其它情况可选 + id 'com.google.devtools.ksp' version '' +} +``` + +在你的 app `build.gradle` 中添加依赖。 + +> 示例如下 + +```gradle +dependencies { + // 基础依赖 + implementation 'com.highcapable.yukihookapi:api:' + // ❗作为 Xposed 模块使用务必添加,其它情况可选 + compileOnly 'de.robv.android.xposed:api:82' + // ❗作为 Xposed 模块使用务必添加,其它情况可选 + ksp 'com.highcapable.yukihookapi:ksp-xposed:' +} +``` + +请将 <version> 修改为 [这里](about/changelog) 的最新版本。 + +!> `YukiHookAPI` 的 `api` 与 `ksp-xposed` 依赖的版本必须一一对应,否则将会造成版本不匹配错误。 + +## 作为 Xposed 模块使用 + +在你的 `AndroidManifest.xml` 中添加基础代码。 + +> 示例如下 + +```xml + + + + + + + + +``` + +在你的项目中创建一个 Hook 入口类,继承于 `YukiHookXposedInitProxy` 并加入注释 `InjectYukiHookWithXposed`。 + +!> 在默认配置情况下,你的入口类需要建立在你的包名的 hook 子包名下,假设你的包名为 `com.example.demo`,入口类应为 `com.example.demo.hook.你的入口类名称`。 + +> 示例如下 + +```kotlin +@InjectYukiHookWithXposed +class MainHook : YukiHookXposedInitProxy { + + override fun onHook() = YukiHookAPI.encase { + // Your code here. + } +} +``` + +然后,你就可以开始编写 Hook 代码了。 + +有关作为 Xposed 模块使用的相关配置详细内容,你可以 [点击这里](config/xposed-using) 继续阅读。 + +## 作为 Hook API 使用 + +### 集成方式 + +创建你的自定义 `Application`。 + +!> 无论使用任何 `Hook Framework`,你都需要加入其对接的 Xposed 依赖支持。 + +!> 若目标 `Hook Framework` 没有集成 Xposed API 你需要自行实现并对接 `XposedBridge`。 + +在 `attachBaseContext` 中添加 `YukiHookAPI.encase` 方法。 + +> 示例如下 + +```kotlin +override fun attachBaseContext(base: Context?) { + // 装载 Hook Framework + // + // Your code here. + // + // 装载 YukiHookAPI + YukiHookAPI.encase(base) { + // Your code here. + } + super.attachBaseContext(base) +} +``` + +然后,你就可以开始编写 Hook 代码了,方式与作为 Xposed 模块使用基本一致。 + +有关作为 Hook API 使用的相关配置详细内容,你可以 [点击这里](config/api-using) 继续阅读。 + +### 特别说明 + +!> 由于你使用了自定义的 Hook 框架而并非模块,~~`YukiHookModuleStatus`~~ ~~`YukiHookModulePrefs`~~ 功能将失效。 \ No newline at end of file diff --git a/docs/guide/special-feature.md b/docs/guide/special-feature.md new file mode 100644 index 00000000..d591b326 --- /dev/null +++ b/docs/guide/special-feature.md @@ -0,0 +1,799 @@ +# 特色功能 + +> 除了基本的 Hook 功能之外,`YukiHookAPI` 还为开发者提供了大量的语法糖和扩展用法。 + +## 字节码扩展功能 + +假设有一个这样的 `Class`。 + +> 示例如下 + +```java +package com.demo; + +public class Test { + + public Test() { + // ... + } + + public Test(boolean isInit) { + // ... + } + + private static TAG = "Test"; + + private String a; + + private boolean a; + + private boolean isTaskRunning = false; + + private static void init() { + // ... + } + + private void doTask(String taskName) { + // ... + } + + private void release(Release release, Function function, Task task) { + // ... + } + + private void stop() { + // ... + } + + private String getName() { + // ... + } + + private void b() { + // ... + } + + private void b(String a) { + // ... + } +} +``` + +### 查询与反射调用 + +假设我们要得到 `doTask` 方法并执行,通常情况下,我们可以使用标准的反射 API 去查询这个方法。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用反射 API 调用并执行 +Test::class.java.getDeclaredMethod("doTask", String::class.java).apply { isAccessible = true }.invoke(instance, "task_name") +``` + +这种写法大概不是很友好,此时 `YukiHookAPI` 就为你提供了一个可在任意地方使用的语法糖。 + +以上写法换做 `YukiHookAPI` 可写作如下形式。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + name = "doTask" + param(StringType) +}.get(instance).call("task_name") +``` + +更多用法可参考 [MethodFinder](api/document?id=methodfinder-class)。 + +同样地,我们需要得到 `isTaskRunning` 变量也可以写作如下形式。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.field { + name = "isTaskRunning" + type = BooleanType +}.get(instance).self // self 为 Field 的实例对象 +``` + +更多用法可参考 [FieldFinder](api/document?id=fieldfinder-class)。 + +也许你还想得到当前 `Class` 的构造方法,同样可以实现。 + +> 示例如下 + +```kotlin +Test::class.java.constructor { + param(BooleanType) +}.get().call(true) // 可创建一个新的实例 +``` + +若想得到的是 `Class` 的无参构造方法,可写作如下形式。 + +> 示例如下 + +```kotlin +Test::class.java.constructor().get().call() // 可创建一个新的实例 +``` + +更多用法可参考 [ConstructorFinder](api/document?id=constructorfinder-class)。 + +### 可选的查询条件 + +假设我们要得到 `Class` 中的 `getName` 方法,可以使用如下实现。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + name = "getName" + returnType = StringType +}.get(instance).string() // 得到方法的结果 +``` + +通过观察发现,这个 `Class` 中只有一个名为 `getName` 的方法,那我们可不可以再简单一点呢? + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + name = "getName" +}.get(instance).string() // 得到方法的结果 +``` + +是的,对于确切不会变化的方法,你可以精简查询条件,`YukiHookAPI` 会默认按照字节码顺序匹配第一个查询到的结果。 + +问题又来了,这个 `Class` 中有一个 `release` 方法,但是它的方法参数好长,而且很多的类型都无法直接得到。 + +通常情况下我们会使用 `param(...)` 来查询这个方法,但是有没有更简单的方法呢。 + +此时,在确定方法唯一性后,你可以使用 `paramCount` 来查询到这个方法。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + name = "release" + // 此时我们不必确定方法参数具体类型,写个数就好 + paramCount = 3 +}.get(instance) // 得到这个方法 +``` + +### 静态字节码 + +有些方法和变量在类中是静态的实现,这个时候,我们不需要传入实例就可以调用它们。 + +假设我们这次要得到静态变量 `TAG` 的内容。 + +> 示例如下 + +```kotlin +Test::class.java.field { + name = "TAG" + type = StringType +}.get().string() // Field 的类型是字符串,可直接进行 cast +``` + +假设类中存在同名的非静态 `TAG` 变量,这个时候怎么办呢? + +加入一个筛选条件即可。 + +> 示例如下 + +```kotlin +Test::class.java.field { + name = "TAG" + type = StringType + modifiers { + // 标识查询的这个变量需要是静态 + asStatic() + } +}.get().string() // Field 的类型是字符串,可直接进行 cast +``` + +更多用法可参考 [ModifierRules](api/document?id=modifierrules-class)。 + +我们还可以调用名为 `init` 的静态方法。 + +> 示例如下 + +```kotlin +Test::class.java.method { + name = "init" +}.get().call() +``` + +同样地,你可以标识它是一个静态。 + +> 示例如下 + +```kotlin +Test::class.java.method { + name = "init" + modifiers { + // 标识查询的这个方法需要是静态 + asStatic() + } +}.get().call() +``` + +### 混淆的字节码 + +你可能已经注意到了,这里给出的示例 `Class` 中有两个混淆的变量名称,它们都是 `a`,这个时候我们要怎么得到它们呢? + +有两种方案。 + +第一种方案,确定变量的名称和类型。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.field { + name = "a" + type = BooleanType +}.get(instance).self // 得到名称为 a 类型为 Boolean 的变量 +``` + +第二种方案,确定变量的类型所在的位置。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.field { + type(BooleanType).index().first() +}.get(instance).self // 得到第一个类型为 Boolean 的变量 +``` + +以上两种情况均可得到对应的变量 `private boolean a`。 + +同样地,这个 `Class` 中也有两个混淆的方法名称,它们都是 `b`。 + +你也可以有两种方案来得到它们。 + +第一种方案,确定方法的名称和方法参数。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + name = "b" + param(StringType) +}.get(instance).call("test_string") // 得到名称为 b 方法参数为 [String] 的方法 +``` + +第二种方案,确定方法的参数所在的位置。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + param(StringType).index().first() +}.get(instance).call("test_string") // 得到第一个方法参数为 [String] 的方法 +``` + +由于观察到这个方法在 `Class` 的最后一个,那我们还有一个备选方案。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + order().index().last() +}.get(instance).call("test_string") // 得到当前 Class 的最后一个方法 +``` + +!> 请尽量不要使用 `order` 来筛选字节码的下标,它们可能是不确定的,除非你确定它在这个 `Class` 中的位置一定不会变。 + +### 直接调用 + +上面介绍的调用字节码的方法都需要使用 `get(instance)` 才能调用对应的方法,有没有简单一点的办法呢? + +此时,你可以在任意实例上使用 `current` 方法来创建一个调用空间。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 假设这个 Class 是不能被直接得到的 +instance.current { + // 执行 doTask 方法 + method { + name = "doTask" + param(StringType) + }.call("task_name") + // 执行 stop 方法 + method { + name = "stop" + }.call() + // 得到 name + val name = method { name = "getName" }.string() +} +``` + +问题又来了,我想使用反射的方式创建如下的实例并调用其中的方法,该怎么做呢? + +> 示例如下 + +```kotlin +Test(true).doTask("task_name") +``` + +通常情况下,我们可以使用标准的反射 API 来调用。 + +> 示例如下 + +```kotlin +classOf("com.demo.Test") + .getDeclaredConstructor(Boolean::class.java) + .apply { isAccessible = true } + .newInstance(true) + .apply { + javaClass + .getDeclaredMethod("doTask", String::class.java) + .apply { isAccessible = true } + .invoke(this, "task_name") + } +``` + +但是感觉这种做法好麻烦,有没有更简洁的调用方法呢? + +这个时候,我们还可以借助 `buildOf` 和 `buildOfAny` 方法来创建一个实例。 + +> 示例如下 + +```kotlin +classOf("com.demo.Test").buildOfAny(true) { param(BooleanType) }?.current { + method { + name = "doTask" + param(StringType) + }.call("task_name") +} +``` + +更多用法可参考 [CurrentClass](api/document?id=currentclass-class) 以及 [buildOf](api/document?id=buildof-method) 方法。 + +### 再次查询 + +假设有三个不同版本的 `Class`,它们都是这个宿主不同版本相同的 `Class`。 + +这里面同样都有一个方法 `doTask`,假设它们的功能是一样的。 + +> 版本 A 示例如下 + +```java +public class Test { + + public void doTask() { + // ... + } +} +``` + +> 版本 B 示例如下 + +```java +public class Test { + + public void doTask(String taskName) { + // ... + } +} +``` + +> 版本 C 示例如下 + +```java +public class Test { + + public void doTask(String taskName, int type) { + // ... + } +} +``` + +我们需要在不同的版本中得到这个相同功能的 `doTask` 方法,要怎么做呢? + +此时,你可以使用 `RemedyPlan` 完成你的需求。 + +> 示例如下 + +```kotlin +// 假设这就是这个 Class 的实例 +val instance = Test() +// 使用 YukiHookAPI 调用并执行 +Test::class.java.method { + name = "doTask" +}.remedys { + method { + name = "doTask" + param(StringType) + }.onFind { + // 可在这里实现找到的逻辑 + } + method { + name = "doTask" + param(StringType, IntType) + }.onFind { + // 可在这里实现找到的逻辑 + } +}.wait(instance) { + // 得到方法的结果 +} +``` + +!> 特别注意使用了 `RemedyPlan` 的方法查询结果不能再使用 `get` 的方式得到方法实例,应当使用 `wait` 方法。 + +更多用法可参考 [Method RemedyPlan](api/document?id=remedyplan-class) 以及 [Constructor RemedyPlan](api/document?id=remedyplan-class-1)。 + +### 相对匹配 + +假设宿主中不同版本中存在功能相同的 `Class` 但仅有 `Class` 的名称不一样。 + +> 版本 A 示例如下 + +```java +public class ATest { + + public static void doTask() { + // ... + } +} +``` + +> 版本 B 示例如下 + +```java +public class BTest { + + public static void doTask() { + // ... + } +} +``` + +这个时候我们想在每个版本都调用这个 `Class` 里的 `doTask` 方法该怎么做呢? + +通常做法是判断 `Class` 是否存在。 + +> 示例如下 + +```kotlin +// 首先查询到这个 Class +val currentClass = if("com.demo.ATest".hasClass) classOf("com.demo.ATest") else classOf("com.demo.BTest") +// 然后再查询这个方法并调用 +currentClass.method { + name = "doTask" +}.get().call() +``` + +感觉这种方案非常的不优雅且繁琐,那么此时 `YukiHookAPI` 就为你提供了一个非常方便的 `VariousClass` 专门来解决这个问题。 + +现在,你可以直接使用以下方式获取到这个 `Class`。 + +> 示例如下 + +```kotlin +VariousClass("com.demo.ATest", "com.demo.BTest").get().method { + name = "doTask" +}.get().call() +``` + +更多用法可参考 [VariousClass](api/document?id=variousclass-class)。 + +若在创建 Hook 的时候使用,可以更加方便,还可以自动拦截找不到 `Class` 的异常。 + +> 示例如下 + +```kotlin +findClass("com.demo.ATest", "com.demo.BTest").hook { + // Your code here. +} +``` + +你还可以把这个 `Class` 定义为一个常量类型来使用。 + +> 示例如下 + +```kotlin +// 定义常量类型 +val ABTestClass = VariousClass("com.demo.ATest", "com.demo.BTest") +// 直接使用 +ABTestClass.hook { + // Your code here. +} +``` + +更多用法可参考 [findClass](api/document?id=findclass-method) 方法。 + +### 注意误区 + +> 这里列举了使用时可能会遇到的误区部分,可供参考。 + +#### 限制性查询条件 + +!> 在查询条件中,除了 `order` 你只能使用一次 `index` 功能。 + +> 示例如下 + +```kotlin +method { + name = "test" + param(BooleanType).index(num = 2) + // ❗错误的使用方法,请仅保留一个 index 方法 + returnType(StringType).index(num = 1) +} +``` + +以下查询条件的使用是没有任何问题的。 + +> 示例如下 + +```kotlin +method { + name = "test" + param(BooleanType).index(num = 2) + order().index(num = 1) +} +``` + +#### 字节码类型 + +!> 在字节码调用结果中,`cast` 方法只能指定字节码对应的类型。 + +例如我们想得到一个 `Boolean` 类型的变量,把他转换为 `String`。 + +以下是错误的使用方法。 + +> 示例如下 + +```kotlin +field { + name = "test" + type = BooleanType +}.get().string() // ❗错误的使用方法,必须 cast 为字节码目标类型 +``` + +以下是正确的使用方法。 + +> 示例如下 + +```kotlin +field { + name = "test" + type = BooleanType +}.get().boolean().toString() // ✅ 正确的使用方法,得到类型后再进行转换 +``` + +## 常用类型扩展功能 + +在查询方法、变量的时候我们通常需要指定所查询的类型。 + +> 示例如下 + +```kotlin +field { + name = "test" + type = Boolean::class.java +} +``` + +在 `Kotlin` 中表达出 `Boolean::class.java` 这个类型的写法很长,感觉并不方便。 + +因此,`YukiHookAPI` 为开发者封装了常见的类型调用,其中包含了 Android 的基本类型和 Java 的基本类型。 + +这个时候上面的类型就可以写作如下形式了。 + +> 示例如下 + +```kotlin +field { + name = "test" + type = BooleanType +} +``` + +在 Java 中常见的基本类型都已被封装为 类型 + Type 的方式,例如 `IntType`、`FloatType`。 + +相应地,数组类型也有方便的使用方法,假设我们要获得 `String[]` 类型的数组。 + +需要写做 `java.lang.reflect.Array.newInstance(String::class.java, 0).javaClass` 才能得到这个类型。 + +感觉是不是很麻烦,这个时候我们可以使用扩展方法 `ArrayClass(StringType)` 来得到这个类型。 + +同时由于 `String` 是常见类型,所以还可以直接使用 `StringArrayClass` 来得到这个类型。 + +一些常见的 Hook 中查询的方法,都有其对应的封装类型以供使用,格式为 类型 + Class。 + +例如 Hook `onCreate` 方法需要查询 `Bundle::class.java` 类型。 + +> 示例如下 + +```kotlin +method { + name = "onCreate" + param(BundleClass) +} +``` + +更多类型请 [点击这里](api/document?id=graphicstypefactory-kt) 前往查看,也欢迎你能贡献更多的常用类型。 + +## 调试日志功能 + +> 日志是调试过程最重要的一环,`YukiHookAPI` 为开发者封装了一套稳定高效的调试日志功能。 + +### 普通日志 + +你可以调用 `loggerD`、`loggerI`、`loggerW` 来向控制台打印普通日志。 + +使用方法如下所示。 + +> 示例如下 + +```kotlin +loggerD(msg = "This is a log") +``` + +此时,`YukiHookAPI` 会调用 `android.util.Log` 与 `XposedBridge.log` 同时打印这条日志。 + +日志默认的 `TAG` 为你在 `YukiHookAPI.Configs.debugTag` 中设置的值。 + +你也可以动态自定义这个值,但是不建议轻易修改 `TAG` 防止过滤不到日志。 + +> 示例如下 + +```kotlin +loggerD(tag = "YukiHookAPI", msg = "This is a log") +``` + +打印的结果为如下所示。 + +> 示例如下 + +``` +[YukiHookAPI][D]--> This is a log +``` + +更多用法可参考 [loggerD](api/document?id=loggerd-method)、[loggerI](api/document?id=loggeri-method) 及 [loggerW](api/document?id=loggerw-method) 方法。 + +### 错误日志 + +你可以调用 `loggerE` 来向控制台打印 `E` 级别的日志。 + +使用方法如下所示。 + +> 示例如下 + +```kotlin +loggerE(msg = "This is an error") +``` + +错误级别的日志是最高的,无论你有没有过滤仅为 `E` 级别的日志。 + +对于错误级别的日志,你还可以在后面加上一个异常堆栈。 + +```kotlin +// 假设这就是被抛出的异常 +val e = Throwable(...) +// 打印日志 +loggerE(msg = "This is an error", throwable = e) +``` + +打印的结果为如下所示。 + +> 示例如下 + +``` +[YukiHookAPI][E]--> This is an error +``` + +同时,日志会帮你打印整个异常堆栈。 + +> 示例如下 + +``` +java.lang.Throwable + at com.demo.Test.(...) + at com.demo.Test.doTask(...) + at com.demo.Test.stop(...) + at com.demo.Test.init(...) + at a.a.a(...) + ... 3 more +``` + +更多用法可参考 [loggerE](api/document?id=loggere-method) 方法。 + +## Xposed 模块数据存储功能 + +> 这是一个自动对接 `SharedPreferences` 和 `XSharedPreferences` 的高效模块数据存储解决方案。 + +我们需要存储模块的数据,以供宿主调用,这个时候会遇到原生 `Sp` 存储的数据互通阻碍。 + +原生的 `Xposed` 给我们提供了一个 `XSharedPreferences` 用于读取模块的 `Sp` 数据。 + +通常情况下我们可以这样在 Hook 内对其进行初始化。 + +> 示例如下 + +```kotlin +XSharedPreferences(BuildConfig.APPLICATION_ID) +``` + +有没有方便快捷的解决方案呢,此时你就可以使用 `YukiHookAPI` 的扩展能力快速实现这个功能。 + +当你在模块中存储数据的时候,若当前处于 `Activity` 内,可以使用如下方法。 + +> 示例如下 + +```kotlin +modulePrefs.putString("test_name", "saved_value") +``` + +当你在 Hook 中读取数据时,可以使用如下方法。 + +> 示例如下 + +```kotlin +val testName = prefs.getString("test_name", "default_value") +``` + +你不需要考虑传入模块的包名以及一系列复杂的权限配置,一切都交给 `YukiHookModulePrefs` 来处理。 + +若要实现存储的区域划分,你可以指定每个 `prefs` 文件的名称。 + +在模块的 `Activity` 中这样使用。 + +> 示例如下 + +```kotlin +// 推荐用法 +modulePrefs("specify_file_name").putString("test_name", "saved_value") +// 也可以这样用 +modulePrefs.name("specify_file_name").putString("test_name", "saved_value") +``` + +在 Hook 中这样读取。 + +> 示例如下 + +```kotlin +// 推荐用法 +val testName = prefs("specify_file_name").getString("test_name", "default_value") +// 也可以这样用 +val testName = prefs.name("specify_file_name").getString("test_name", "default_value") +``` + +若你的项目中有大量的固定数据需要存储和读取,推荐使用 `PrefsData` 来创建模板,详细用法可参考 [PrefsData](api/document?id=prefsdata-class)。 + +更多用法可参考 [YukiHookModulePrefs](api/document?id=yukihookmoduleprefs-class)。 \ No newline at end of file diff --git a/docs/icon.png b/docs/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7576340dc8d4839aefac808e9e68a48598e08414 GIT binary patch literal 24264 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 XpOeE> + + + + + + YukiHookAPI - 轻量、高效、稳定的 Xposed Hook API + + + + + + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/sw.js b/docs/sw.js new file mode 100644 index 00000000..0029c532 --- /dev/null +++ b/docs/sw.js @@ -0,0 +1,90 @@ +/* =========================================================== + * docsify sw.js + * =========================================================== + * Copyright 2016 @huxpro + * Licensed under Apache 2.0 + * Register service worker. + * ========================================================== */ + +const RUNTIME = 'docsify' +const HOSTNAME_WHITELIST = [ + self.location.hostname, + 'fonts.gstatic.com', + 'fonts.googleapis.com', + 'unpkg.com', +] + +// The Util Function to hack URLs of intercepted requests +const getFixedUrl = (req) => { + var now = Date.now() + var url = new URL(req.url) + + // 1. fixed http URL + // Just keep syncing with location.protocol + // fetch(httpURL) belongs to active mixed content. + // And fetch(httpRequest) is not supported yet. + url.protocol = self.location.protocol + + // 2. add query for caching-busting. + // GitHub Pages served with Cache-Control: max-age=600 + // max-age on mutable content is error-prone, with SW life of bugs can even extend. + // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. + // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 + if (url.hostname === self.location.hostname) { + url.search += (url.search ? '&' : '?') + 'cache-bust=' + now + } + return url.href +} + +/** + * @Lifecycle Activate + * New one activated when old isn't being used. + * + * waitUntil(): activating ====> activated + */ +self.addEventListener('activate', (event) => { + event.waitUntil(self.clients.claim()) +}) + +/** + * @Functional Fetch + * All network requests are being intercepted here. + * + * void respondWith(Promise r) + */ +self.addEventListener('fetch', (event) => { + // Skip some of cross-origin requests, like those for Google Analytics. + if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) { + // Stale-while-revalidate + // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale + // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1 + const cached = caches.match(event.request) + const fixedUrl = getFixedUrl(event.request) + const fetched = fetch(fixedUrl, { cache: 'no-store' }) + const fetchedCopy = fetched.then((resp) => resp.clone()) + + // Call respondWith() with whatever we get first. + // If the fetch fails ('e.g' disconnected), wait for the cache. + // If there’s nothing in cache, wait for the fetch. + // If neither yields a response, return offline pages. + event.respondWith( + Promise.race([fetched.catch((_) => cached), cached]) + .then((resp) => resp || fetched) + .catch((_) => { + /* eat any errors */ + }), + ) + + // Update the cache with the version we fetched (only for ok status) + event.waitUntil( + Promise.all([fetchedCopy, caches.open(RUNTIME)]) + .then( + ([response, cache]) => + response.ok && cache.put(event.request, response), + ) + .catch((_) => { + /* eat any errors */ + }), + ) + } +}) \ No newline at end of file