diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/.vscode/settings.json b/docs/.vscode/settings.json deleted file mode 100644 index 3b664107..00000000 --- a/docs/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "git.ignoreLimitWarning": true -} \ No newline at end of file diff --git a/docs/_404.md b/docs/_404.md deleted file mode 100644 index 146be7d1..00000000 --- a/docs/_404.md +++ /dev/null @@ -1,3 +0,0 @@ -# 404 - 没有找到此页面 - - 请尝试重新输入你需要的页面或通过导航前往指定文档页面。 \ No newline at end of file diff --git a/docs/_coverpage.md b/docs/_coverpage.md deleted file mode 100644 index 3a0c1a69..00000000 --- a/docs/_coverpage.md +++ /dev/null @@ -1,20 +0,0 @@ -

- -
- -# Yuki Hook API - -> 一个使用 Kotlin 重构的轻量、高效、稳定的 Xposed Hook API - -- 致力于为 Xposed 模块开发更加便捷而打造的零学习成本框架 - -- 轻量优雅 高效调试 - -- 方便移植 快速上手 - -`更新时间 2022-07-23 22:33` - -[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 deleted file mode 100644 index 11d13ee7..00000000 --- a/docs/_navbar.md +++ /dev/null @@ -1,21 +0,0 @@ -* 入门 - * [介绍](guide/home) - * [基础知识](guide/knowledge) - * [快速开始](guide/quick-start) - * [用法示例](guide/example) - * [特色功能](guide/special-feature) - * [从 Xposed API 迁移](guide/move-to-new-api) - -* 配置 - * [API 基本配置](config/api-example) - * [API 异常处理](config/api-exception) - * [R8 与 Proguard 混淆](config/r8-proguard) - -* 工具 - * [YukiHookAPI 构建工具](tools/yukihookapi-projectbuilder) - -* 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 deleted file mode 100644 index 08e3e632..00000000 --- a/docs/_sidebar.md +++ /dev/null @@ -1,27 +0,0 @@ -* 入门 - * [介绍](guide/home) - * [基础知识](guide/knowledge) - * [快速开始](guide/quick-start) - * [用法示例](guide/example) - * [特色功能](guide/special-feature) - * [从 Xposed API 迁移](guide/move-to-new-api) - -* 配置 - * [API 基本配置](config/api-example) - * [API 异常处理](config/api-exception) - * [作为 Xposed 模块使用的相关配置](config/xposed-using) - * [作为 Hook API 使用的相关配置](config/api-using) - * [R8 与 Proguard 混淆](config/r8-proguard) - -* 工具 - * [YukiHookAPI 构建工具](tools/yukihookapi-projectbuilder) - -* 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 deleted file mode 100644 index 7212a5ca..00000000 --- a/docs/about/about.md +++ /dev/null @@ -1,35 +0,0 @@ -# 关于此文档 - -> 此文档由 [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 deleted file mode 100644 index 5d13f128..00000000 --- a/docs/about/changelog.md +++ /dev/null @@ -1,269 +0,0 @@ -# 更新日志 - -> 这里记录了 `YukiHookAPI` 的版本更新历史。 - -### 1.0.92 | 2022.05.31 - -- 修正了大量方法中 callback 的命名方法 -- 更换方案再次修复 `YukiHookDataChannel` 在低于 **Android 12** 的设备上不能回调当前 `Activity` 广播的问题 -- `InjectYukiHookWithXposed` 注解新增 `isUsingResourcesHook` 功能,现在你可以选择性关闭自动生成 `IXposedHookInitPackageResources` 的依赖接口了 - -### 1.0.91 | 2022.05.29 - -- 修复部分设备的定制系统在 LSPosed 环境下开机启动获取的 `ClassLoader` 错误的问题,感谢 [Luckyzyx](https://github.com/luckyzyx) 的反馈 -- 修复 `YukiHookDataChannel` 在 **ZUI** 以及低于 **Android 12** 的系统上不能回调当前 `Activity` 广播的问题 -- 整合 `YukiHookModuleStatus` 功能到 `YukiHookAPI.Status`,重写了大量方法,现在你可以在模块与宿主中双向判断模块激活等状态信息 - -### 1.0.90 | 2022.05.27 - -- 修复 `YukiHookDataChannel` 在模块设置监听回调时闪退的问题 -- 修复 `YukiHookDataChannel` 在非当前 `Activity` 情况下依然会回调的问题 -- 移除 `YukiHookDataChannel` 回调事件的默认值,没有即不回调 -- 移除 `YukiHookModulePrefs` 在 XShare 不可读的情况下打印的警告 -- 新增 `YukiHookModulePrefs` 中的 `isXSharePrefsReadable` 方法,可判断当前的 XShare 是否可用 - -### 1.0.89 | 2022.05.26 - -- 修复 `YukiHookDataChannel` 不能重复设置监听的问题,并加入在模块不同 `Activity` 中重复响应和自动跟随 `Activity` 销毁监听功能 -- 新增 `YukiHookDataChannel` 重复监听用例说明文档 -- 加入 `onAlreadyHooked` 方法,可判断当前方法是否被重复 Hook -- 修改部分重复添加 HashMap 的逻辑,移除 `putIfAbsent` 方法,允许覆盖添加 -- 修复了几处可能的 BUG - -### 1.0.88 | 2022.05.25 - -- 对 Xposed API 完全解耦合 -- 增加了 `type` 中的 `android` 类型 -- 将 `YukiHookModuleStatus` 从自动生成代码中分离,并加入 `isEnableHookModuleStatus` 的开关,由你决定是否启用 -- 对 API 大量类的构造方法进行了 internal 闭包处理 -- 将 `YukiHookModulePrefs` 设置为单例运行,防止重复创建浪费系统资源 -- 修复自 `1.0.80` 版本后无法嵌套 Hook 的 BUG,并优化嵌套 Hook 相关功能 -- 修改 Hooker 存储方案由 HashSet 到 HashMap,防止重复添加 Hooker 的问题 -- 修改 Hook 核心实现方法,加入查重,避免重复 Hook 多次回调 `HookParam` 方法 -- `MethodFinder` 与 `FieldFinder` 加入查找模糊方法、变量名称功能,可调用 `name { ... }` 来设置查找条件,支持正则 -- 优化并修改 `appContext` 的获取方式,降低会取到空的问题的可能性 -- 修改自动生成的代码中 `logger` 的打印 `TAG` 默认为你自定义的名称,方便进行调试 -- 优化 `YukiHookBridge` 的 `Hooker` 实现方式,提升 Hook 性能 -- `PackageParam` 增加 `onAppLifecycle` 方法,可原生监听宿主的生命周期以及实现注册系统广播功能 -- 新增 `YukiHookDataChannel` 功能,可在模块与宿主保持存活的情况下使用系统无序广播进行通讯 -- `YukiHookDataChannel` 增加 `checkingVersionEquals` 方法,可通过监听来验证模块更新后宿主并未更新版本不匹配问题 -- `demo-module` 的示例代码中新增 Java 版本的示例,仅供参考 - -### 1.0.87 | 2022.05.10 - -- 新增 `refreshModuleAppResources` 功能,以适配语言区域、字体大小、分辨率改变等情况下的 Resources 刷新 -- 新增 `isEnableModuleAppResourcesCache` 功能,可自行设置是否自动缓存当前模块的 Resources - -### 1.0.86 | 2022.05.06 - -- 修复不支持 Resources Hook(资源钩子) 的情况下在 `initZygote` 时持续报错的问题,复现在 **ZUI**/**LSPosed CI(1.8.3-6550)** -- 优化并对 Resources Hook 进行异常处理,只有被使用后不支持才会打印错误和警告 - -### 1.0.85 | 2022.05.04 - -- 修复无法 Hook 系统框架的严重问题,从 `1.0.80` 开始出现 -- 调试日志中新增区分 `initZygote` 装载的包名为 `android-zygote`,`packageName` 保持 `android` 不变 - -### 1.0.83 | 2022.05.04 - -- 修复 `YukiHookModuleStatus` 在 `loadSystem` 后大量报错的问题 -- 新增 `type` 中的 `android` 类型 -- 更新帮助文档的示例说明 - -### 1.0.82 | 2022.05.04 - -- 修复了一处概念混淆错误,区分 `initZygote` 与系统框架的关系,之前的注释和文档有问题,非常抱歉 -- `PackageParam` 新增 `loadSystem` 方法,不需要再写 `loadApp(name = "android")` 即可 Hook 系统框架 - -### 1.0.81 | 2022.05.04 - -- 修复使用 `by` 方法设置条件后 Hook 方法体内查找不到的方法、构造方法依然输出错误日志的问题 -- 在执行 Hook 过程中加入全局日志显示当前 Hook APP 的包名,并修复一处错误日志打印样式的问题 - -### 1.0.80 | 2022.05.01 - -- `InjectYukiHookWithXposed` 注解新增 `entryClassName` 功能,可自定义生成的 `xposed_init` 入口类名 -- ~~`YukiHookXposedInitProxy`~~ 更名为 `IYukiHookXposedInit`,原接口名称已作废,将在后续版本中直接被删除 -- 新增 `initZygote` 与 Resources Hook 功能,支持 Hook Layout -- 新增 `onXposedEvent` 方法,可监听原生 Xposed API 的全部事件 -- 对 Hook 功能的 `lambda` 进行 `inline` 处理,避免生成过碎的匿名类,提升编译后的运行性能 -- 修复 `PrefsData` 编译后的方法体复制过大的问题 -- 增加 `XSharePreference` 可读性测试,失败后会自动打印警告日志 -- `PackageParam` 新增 `appResources`、`moduleAppResources`、`moduleAppFilePath` 功能 -- `PackageParam` 的 `loadApp` 新增不写 `name` 功能,默认筛选全部 APP -- `PackageParam` 新增 `loadZygote` 方法,可直接 Hook 系统框架 -- `PackageParam` 新增 `resources().hook` 功能 -- 优化方法、构造方法、变量查找功能,找不到的错误日志将优先显示已设置的查询条件 -- 增加 `hasExtends` 扩展方法,可判断当前 `Class` 是否有继承关系 -- 增加 `isSupportResourcesHook` 功能,判断当前是否支持资源钩子(Resources Hook) -- `current` 功能新增 `superClass` 方法调用父类 -- 查找方法、构造方法、变量新增 `superClass` 查询条件,可继续在父类中查找 -- `YukiHookAPI` 大量方法与 Xposed API 解耦合 -- 新增 Xposed API 的原生 Hook 优先级功能 -- 修复 `isFirstApplication` 可能判断不准确的问题 -- 屏蔽 MIUI 系统上 MiuiCatcherPatch 重复调用 Hook 入口方法的问题 -- 优化 Hook 入口调用方法,避免因为 Hook Framework 问题导致多次调用 -- 修复 Hook `ClassLoader` 导致 Hook 卡死的问题,感谢 [WankkoRee](https://github.com/WankkoRee) 的反馈 -- 提升 `XC_Callback` 接口对接后的性能 -- Java `type` 新增 `ClassLoader` 类型 -- 优化 API 帮助文档,修复可能持续缓存页面的问题 - -### 1.0.78 | 2022.04.18 - -- `YukiHookModulePrefs` 新增 `isRunInNewXShareMode` 方法,可用于判断模块当前是否处于 `New XSharePreference` 模式 -- 修复 `YukiHookModulePrefs` 在 `New XSharePreference` 模式下工作的部分问题 -- 新增 `ModulePreferenceFragment`,现在,你可以完全替换掉 `PreferenceFragmentCompat` 并开始使用新的功能 -- 适配 `PreferenceFragmentCompat` 的 Sp 数据存储解决方案,感谢 [mahoshojoHCG](https://github.com/mahoshojoHCG) 的反馈 -- 更新自动处理程序以及 `Kotlin` 依赖到最新版本 -- 修正部分文档和代码注释中的错误 - -### 1.0.77 | 2022.04.15 - -- `YukiHookModulePrefs` 新增 `clear` 方法,感谢 [WankkoRee](https://github.com/WankkoRee) 的建议 -- `YukiHookModulePrefs` 新增 `getStringSet`、`putStringSet`、`all` 方法 -- `HookParam` 的 `args` 增加 `any` 方法 -- 新增 `ModuleApplication`,可在模块中继承此类实现更多功能 -- 对接全部的 `findClass` 功能到 Xposed API,在非宿主环境继续使用原生 `ClassLoader` -- 修复了一些可能存在的 BUG - -### 1.0.75 | 2022.04.13 - -- 更正了自动处理程序的逻辑识别部分,感谢 [ApeaSuperz](https://github.com/ApeaSuperz) 的贡献 -- 修正一处文档注释的引用未更改的问题 -- `HookParam` 中删除了 `firstArgs` 与 `lastArgs` 方法,现在你可以使用 `args().first()` 与 `args().last()` 来取代它 -- `HookParam` 中删除了 `args()` 中的默认参数 `index = 0`,现在你可以使用 `args().first()` 或 `args(index = 0)` 来取代它 -- `HookParam` 中 `result` 功能增加了泛型匹配,现在你可以使用 `result` 来匹配你的目标方法已知返回值类型了 -- 方法、构造方法查询功能新增 `emptyParam` 条件,并完善了文档相关需要注意的查询条件误区 -- 增加了 `type` 中的 `android` 类型 - -### 1.0.73 | 2022.04.10 - -- 修正几处文档的中文翻译错误,感谢 [WankkoRee](https://github.com/WankkoRee) 的贡献 -- 修复在某些情况下 `XC_LoadPackage.LoadPackageParam` 内容为空抛出异常的问题,感谢 [Luckyzyx](https://github.com/luckyzyx) 的反馈 -- 修复一些已知的 BUG,提升 Hook 稳定性 - -### 1.0.72 | 2022.04.09 - -- 更新 API 文档到新的地址 -- `PackageParam` 中加入 `appContext` 功能 -- 修复一些已知的 BUG,提升 Hook 稳定性 - -### 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 - -

-[浏览下一篇  ➡️](about/future.md) \ No newline at end of file diff --git a/docs/about/contacts.md b/docs/about/contacts.md deleted file mode 100644 index 5b3a5d36..00000000 --- a/docs/about/contacts.md +++ /dev/null @@ -1,14 +0,0 @@ -# 联系我们 - -> 如在使用中有任何问题,或有任何建设性的建议,都可以联系我们。 - -加入我们 [点击加入 Telegram 群组](https://t.me/YukiHookAPI) - -在**酷安**找到我 [@星夜不荟](http://www.coolapk.com/u/876977) - -## 助力维护 - -感谢您选择并使用 `YukiHookAPI`,如有代码相关的建议和请求,可在 Github 提交 Pull Request。 - -

-[浏览下一篇  ➡️](about/about.md) \ No newline at end of file diff --git a/docs/about/future.md b/docs/about/future.md deleted file mode 100644 index 15cab8b4..00000000 --- a/docs/about/future.md +++ /dev/null @@ -1,38 +0,0 @@ -# 展望未来 - -> 未来是美好的,也是不确定的,让我们共同期待 `YukiHookAPI` 在未来的发展空间。 - -## 未解决的问题 - -> 这里收录了 `YukiHookAPI` 尚未解决的问题。 - -### YukiHookModulePrefs - -目前仅限完美支持 LSPosed,其它 Xposed 框架需要降级模块 API。 - -可能完全不支持太极,太极在高版本系统上需要更低的 API 才能适配。 - -部分 Xposed 模块开发者目前选择 Hook 目标 APP 内置 Sp 存储方案解决模块设置共享问题。 - -后期 Android 系统的权限将越来越严格,`selinux` 就是目前面临的一个大问题,有待讨论和研究。 - -## 未来的计划 - -> 这里收录了 `YukiHookAPI` 可能会在后期添加的功能。 - -### 支持独立使用的 Lite 版本 - -**计划状态:待定** - -目前 API 只支持通过自动处理程序绑定到 `xposed_init`,若您不喜欢自动处理程序,一定要自己实现模块装载入口,未来会按照需求人数推出仅有 API 功能的 Lite 版本。 - -API 已经提供了 Xposed 原生 API 监听接口,你可以 [在这里](config/xposed-using?id=原生-xposed-api-事件) 找到或查看 Demo 的实现方法。 - -### 支持更多 Hook Framework - -作为 API 来讲,目前仅仅对接 `XposedBridge` 作为兼容层,还是有一定的局限性。 - -大部分 `inline hook` 没有 `Java` 兼容层,后期可能会考虑 `native hook` 的 `Java` 兼容层适配。 - -

-[浏览下一篇  ➡️](about/contacts.md) \ No newline at end of file diff --git a/docs/api/document.md b/docs/api/document.md deleted file mode 100644 index 14c30c3a..00000000 --- a/docs/api/document.md +++ /dev/null @@ -1,84 +0,0 @@ -# Public API - -> 以下内容为公开的 API 接口,在版本变更时将同步至最新。 - -[filename](public/YukiHookAPI.md ':include') - -[filename](public/PackageParam.md ':include') - -[filename](public/HookParam.md ':include') - -[filename](public/InjectYukiHookWithXposed.md ':include') - -[filename](public/IYukiHookXposedInit.md ':include') - -[filename](public/YukiHookModulePrefs.md ':include') - -[filename](public/ModulePreferenceFragment.md ':include') - -[filename](public/PrefsData.md ':include') - -[filename](public/YukiHookDataChannel.md ':include') - -[filename](public/ChannelData.md ':include') - -[filename](public/ModuleApplication.md ':include') - -[filename](public/ModuleAppActivity.md ':include') - -[filename](public/ModuleAppCompatActivity.md ':include') - -[filename](public/ModuleContextThemeWrapper.md ':include') - -[filename](public/YukiModuleResources.md ':include') - -[filename](public/YukiResources.md ':include') - -[filename](public/YukiResForwarder.md ':include') - -[filename](public/YukiXposedEvent.md ':include') - -[filename](public/ComponentTypeFactory.md ':include') - -[filename](public/GraphicsTypeFactory.md ':include') - -[filename](public/ViewTypeFactory.md ':include') - -[filename](public/VariableTypeFactory.md ':include') - -[filename](public/DefinedTypeFactory.md ':include') - -[filename](public/LoggerFactory.md ':include') - -[filename](public/ReflectionFactory.md ':include') - -[filename](public/YukiHookFactory.md ':include') - -[filename](public/YukiBaseHooker.md ':include') - -[filename](public/YukiMemberHookCreator.md ':include') - -[filename](public/YukiResourcesHookCreator.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/NameRules.md ':include') - -[filename](public/HookClass.md ':include') - -[filename](public/VariousClass.md ':include') - -[filename](public/CurrentClass.md ':include') - -[filename](public/HookResources.md ':include') - -

-[浏览下一篇  ➡️](about/changelog.md) \ No newline at end of file diff --git a/docs/api/home.md b/docs/api/home.md deleted file mode 100644 index dd57e765..00000000 --- a/docs/api/home.md +++ /dev/null @@ -1,52 +0,0 @@ -# 文档介绍 - -> 这里的文档将同步最新 API 版本的相关用法,请保持 `YukiHookAPI` 为最新版本以使用最新版本的功能。 - -## 功能描述说明 - -> 功能描述主要介绍当前 API 的相关用法和用途。 - -## 功能示例说明 - -> 功能示例主要展示了当前 API 的基本用法示例,可供参考。 - -## 变更记录说明 - -首个版本的功能将标记为 `v$version` `添加`; - -后期新增加的功能将标记为 `v$version` `新增`; - -后期修改的功能将被追加为 `v$version` `修改`; - -后期被作废的功能将标记为 `v$version` `作废` 并会标注删除线; - -后期被删除的功能将标记为 `v$version` `移除` 并会标注删除线。 - -## 相关符号说明 - -- *kt*  Kotlin Static File - -- *annotation*  注解 - -- *interface*  接口 - -- *object*  类 (单例) - -- *class*  类 - -- *field*  变量或 `get`、`set` 方法或只读的 `get` 方法 - -- *method*  方法 - -- *enum*  Enum 常量 - -- *ext-field*  扩展的变量 (全局) - -- *ext-method*  扩展的方法 (全局) - -- *i-ext-field*  扩展的变量 (调用域限制) - -- *i-ext-method*  扩展的方法 (调用域限制) - -

-[浏览下一篇  ➡️](api/document) \ No newline at end of file diff --git a/docs/api/public/BaseFinder-IndexTypeCondition.md b/docs/api/public/BaseFinder-IndexTypeCondition.md deleted file mode 100644 index f6fa07ff..00000000 --- a/docs/api/public/BaseFinder-IndexTypeCondition.md +++ /dev/null @@ -1,101 +0,0 @@ -## BaseFinder.IndexTypeCondition *- class* - -```kotlin -inner class IndexTypeCondition internal constructor(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 internal constructor() -``` - -**变更记录** - -`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/ChannelData.md b/docs/api/public/ChannelData.md deleted file mode 100644 index 0346bf48..00000000 --- a/docs/api/public/ChannelData.md +++ /dev/null @@ -1,64 +0,0 @@ -## ChannelData *- class* - -```kotlin -data class ChannelData(var key: String, var value: T?) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 数据通讯桥键值构造类。 - -这个类是对 `YukiHookDataChannel` 的一个扩展用法。 - -**功能示例** - -建立一个模板类定义模块与宿主需要发送的键值数据。 - -> 示例如下 - -```kotlin -object DataConst { - - val TEST_KV_DATA_1 = ChannelData("test_data_1", "defalut value") - val TEST_KV_DATA_2 = ChannelData("test_data_2", 0) -} -``` - -键值数据定义后,你就可以方便地在模块和宿主中调用所需要发送的数据。 - -> 模块示例如下 - -```kotlin -// 从指定包名的宿主获取 -dataChannel(packageName = "com.example.demo").wait(DataConst.TEST_KV_DATA_1) { value -> - // Your code here. -} -// 发送给指定包名的宿主 - 未填写 value 时将使用模板提供的默认值 -dataChannel(packageName = "com.example.demo").put(DataConst.TEST_KV_DATA_1, value = "sending value") -``` - -> 宿主示例如下 - -```kotlin -// 从模块获取 -dataChannel.wait(DataConst.TEST_KV_DATA_1) { value -> - // Your code here. -} -// 发送给模块 - 未填写 value 时将使用模板提供的默认值 -dataChannel.put(DataConst.TEST_KV_DATA_1, value = "sending value") -``` - -你依然可以不使用模板定义的默认值,随时修改你的默认值。 - -> 示例如下 - -```kotlin -// 获取 - 此时 value 取到的默认值将会是 2 - 并不是模板提供的 0 -dataChannel.wait(DataConst.TEST_KV_DATA_2, value = 2) { value -> - // Your code here. -} -``` \ No newline at end of file diff --git a/docs/api/public/ComponentTypeFactory.md b/docs/api/public/ComponentTypeFactory.md deleted file mode 100644 index dd4f003d..00000000 --- a/docs/api/public/ComponentTypeFactory.md +++ /dev/null @@ -1,11 +0,0 @@ -## 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 deleted file mode 100644 index 7b5e795f..00000000 --- a/docs/api/public/ConstructorFinder.md +++ /dev/null @@ -1,708 +0,0 @@ -## ConstructorFinder *- class* - -```kotlin -class ConstructorFinder internal constructor(override val hookInstance: YukiMemberHookCreator.MemberHookCreator?, override val classSet: Class<*>) : MemberBaseFinder -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.2` `修改` - -合并到 `BaseFinder` - -`v1.1.0` `修改` - -合并到 `MemberBaseFinder` - -**功能描述** - -> `Constructor` 查找类。 - -可通过指定类型查找指定 `Constructor` 或一组 `Constructor`。 - -### paramCount *- field* - -```kotlin -var paramCount: Int -``` - -**变更记录** - -`v1.0.67` `新增` - -**功能描述** - -> 设置 `Constructor` 参数个数。 - -你可以不使用 `param` 指定参数类型而是仅使用此变量指定参数个数。 - -若参数个数小于零则忽略并使用 `param`。 - -### modifiers *- method* - -```kotlin -fun modifiers(conditions: ModifierConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.0.67` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `修改` - -合并到 `ModifierConditions` - -**功能描述** - -> 设置 `Constructor` 标识符筛选条件。 - -可不设置筛选条件,默认模糊查找并取第一个匹配的 `Constructor`。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### emptyParam *- method* - -```kotlin -fun emptyParam(): IndexTypeCondition -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 设置 `Constructor` 空参数、无参数。 - -### param *- method* - -```kotlin -fun param(vararg paramType: Any): IndexTypeCondition -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 设置 `Constructor` 参数。 - -如果同时使用了 `paramCount` 则 `paramType` 的数量必须与 `paramCount` 完全匹配。 - -如果 `Constructor` 中存在一些无意义又很长的类型,你可以使用 `VagueType` 来替代它。 - -!> 无参 `Constructor` 请使用 `emptyParam` 设置查询条件。 - -!> 有参 `Constructor` 必须使用此方法设定参数或使用 `paramCount` 指定个数。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### paramCount *- method* - -```kotlin -fun paramCount(num: Int): IndexTypeCondition -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 设置 `Constructor` 参数个数。 - -你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数。 - -若参数个数小于零则忽略并使用 `param`。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### paramCount *- method* - -```kotlin -fun paramCount(numRange: IntRange): IndexTypeCondition -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置 `Constructor` 参数个数范围。 - -你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数范围。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### paramCount *- method* - -```kotlin -fun paramCount(conditions: CountConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置 `Constructor` 参数个数条件。 - -你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### superClass *- method* - -```kotlin -fun superClass(isOnlySuperClass: Boolean) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置在 `classSet` 的所有父类中查找当前 `Constructor`。 - -!> 若当前 `classSet` 的父类较多可能会耗时,API 会自动循环到父类继承是 `Any` 前的最后一个类。 - -### RemedyPlan *- class* - -```kotlin -inner class RemedyPlan internal constructor() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> `Constructor` 重查找实现类,可累计失败次数直到查找成功。 - -#### constructor *- method* - -```kotlin -inline fun constructor(initiate: ConstructorConditions) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建需要重新查找的 `Constructor`。 - -你可以添加多个备选 `Constructor`,直到成功为止,若最后依然失败,将停止查找并输出错误日志。 - -#### Result *- class* - -```kotlin -inner class Result internal constructor() -``` - -**变更记录** - -`v1.0.1` `新增` - -**功能描述** - -> `RemedyPlan` 结果实现类。 - -##### onFind *- method* - -```kotlin -fun onFind(initiate: HashSet>.() -> Unit) -``` - -**变更记录** - -`v1.0.1` `新增` - -`v1.1.0` `修改` - -`initiate` 参数 `Constructor` 变为 `HashSet` - -**功能描述** - -> 当在 `RemedyPlan` 中找到结果时。 - -**功能示例** - -你可以方便地对重查找的 `Constructor` 实现 `onFind` 方法。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.onFind { - // Your code here. -} -``` - -### Process *- class* - -```kotlin -inner class Process internal constructor(internal val isNoSuch: Boolean, internal val throwable: Throwable?) : BaseResult -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> `Constructor` 查找结果处理类,为 `hookInstance` 提供。 - -#### result *- method* - -```kotlin -inline fun result(initiate: Process.() -> Unit): Process -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 创建监听结果事件方法体。 - -**功能示例** - -你可以使用 `lambda` 形式创建 `Result` 类。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.result { - all() - remedys {} - onNoSuchConstructor {} -} -``` - -#### all *- method* - -```kotlin -fun all(): Process -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置全部查询条件匹配的多个 `Constructor` 实例结果到 `hookInstance`。 - -#### remedys *- method* - -```kotlin -inline fun remedys(initiate: RemedyPlan.() -> Unit): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 创建 `Constructor` 重查找功能。 - -**功能示例** - -当你遇到一种 `Constructor` 可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchConstructor` 捕获异常二次查找 `Constructor`。 - -若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.remedys { - constructor { - // Your code here. - } - constructor { - // Your code here. - } -} -``` - -#### onNoSuchConstructor *- method* - -```kotlin -inline fun onNoSuchConstructor(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 监听找不到 `Constructor` 时。 - -只会返回第一次的错误信息,不会返回 `RemedyPlan` 的错误信息。 - -### Result *- class* - -```kotlin -inner class Result internal constructor(internal val isNoSuch: Boolean, internal val throwable: Throwable?) : BaseResult -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -继承到接口 `BaseResult` - -**功能描述** - -> `Constructor` 查找结果实现类。 - -#### result *- method* - -```kotlin -inline fun result(initiate: Result.() -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建监听结果事件方法体。 - -**功能示例** - -你可以使用 `lambda` 形式创建 `Result` 类。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.result { - get().call() - all() - remedys {} - onNoSuchConstructor {} -} -``` - -#### get *- method* - -```kotlin -fun get(): Instance -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 获得 `Constructor` 实例处理类。 - -若有多个 `Constructor` 结果只会返回第一个。 - -!> 若你设置了 `remedys` 请使用 `wait` 回调结果方法。 - -**功能示例** - -你可以通过获得方法所在实例来执行 `Constructor` 创建新的实例对象。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.get().call() -``` - -你可以 `cast` 构造方法为指定类型的实例对象。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.get().newInstance() -``` - -!> 若构造方法含有参数则后方参数必填。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.get().newInstance("param1", "param2") -``` - -#### all *- method* - -```kotlin -fun all(): ArrayList -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Constructor` 实例处理类数组。 - -返回全部查询条件匹配的多个 `Constructor` 实例结果。 - -**功能示例** - -你可以通过此方法来获得当前条件结果中匹配的全部 `Constructor`。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.all().forEach { instance -> - instance.call(...) -} -``` - -#### give *- method* - -```kotlin -fun give(): Constructor<*>? -``` - -**变更记录** - -`v1.0.67` `新增` - -**功能描述** - -> 得到 `Constructor` 本身。 - -若有多个 `Constructor` 结果只会返回第一个。 - -在查询条件找不到任何结果的时候将返回 `null`。 - -#### giveAll *- method* - -```kotlin -fun giveAll(): HashSet> -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 得到 `Constructor` 本身数组。 - -返回全部查询条件匹配的多个 `Constructor` 实例。 - -在查询条件找不到任何结果的时候将返回空的 `HashSet`。 - -#### wait *- method* - -```kotlin -fun wait(initiate: Instance.() -> Unit) -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 获得 `Constructor` 实例处理类,配合 `RemedyPlan` 使用。 - -若有多个 `Constructor` 结果只会返回第一个。 - -!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 - -!> 若你没有设置 `remedys` 此方法将不会被回调。 - -#### waitAll *- method* - -```kotlin -fun waitAll(initiate: ArrayList.() -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Constructor` 实例处理类数组,配合 `RemedyPlan` 使用。 - -返回全部查询条件匹配的多个 `Constructor` 实例结果。 - -!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 - -!> 若你没有设置 `remedys` 此方法将不会被回调。 - -#### remedys *- method* - -```kotlin -inline fun remedys(initiate: RemedyPlan.() -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建 `Constructor` 重查找功能。 - -**功能示例** - -当你遇到一种 `Constructor` 可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchConstructor` 捕获异常二次查找 `Constructor`。 - -若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 - -> 示例如下 - -```kotlin -constructor { - // Your code here. -}.remedys { - constructor { - // Your code here. - } - constructor { - // Your code here. - } -} -``` - -#### onNoSuchConstructor *- method* - -```kotlin -inline fun onNoSuchConstructor(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 监听找不到 `Constructor` 时。 - -只会返回第一次的错误信息,不会返回 `RemedyPlan` 的错误信息。 - -#### ignored *- method* - -```kotlin -fun ignored(): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 忽略异常并停止打印任何错误日志。 - -若 `isNotIgnoredHookingFailure` 为 `false` 则自动忽略。 - -!> 此时若要监听异常结果,你需要手动实现 `onNoSuchConstructor` 方法。 - -#### ~~ignoredError *- method*~~ - -**变更记录** - -`v1.0.3` `新增` - -`v1.1.0` `作废` - -请转移到新方法 `ignored()` - -#### Instance *- class* - -```kotlin -inner class Instance internal constructor(private val constructor: Constructor<*>?) -``` - -**变更记录** - -`v1.0.2` `新增` - -`v1.1.0` `修改` - -新增 `constructor` 参数 - -**功能描述** - -> `Constructor` 实例处理类。 - -##### call *- method* - -```kotlin -fun call(vararg param: Any?): Any? -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 执行 `Constructor` 创建目标实例,不指定目标实例类型。 - -##### newInstance *- method* - -```kotlin -fun newInstance(vararg param: Any?): T? -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 执行 `Constructor` 创建目标实例 ,指定 `T` 目标实例类型。 \ No newline at end of file diff --git a/docs/api/public/CurrentClass.md b/docs/api/public/CurrentClass.md deleted file mode 100644 index 254acbd2..00000000 --- a/docs/api/public/CurrentClass.md +++ /dev/null @@ -1,157 +0,0 @@ -## CurrentClass *- class* - -```kotlin -class CurrentClass internal constructor(internal val classSet: Class<*>, internal val instance: Any) -``` - -**变更记录** - -`v1.0.70` `新增` - -`v1.1.0` `修改` - -调整了构造方法的参数名称 - -**功能描述** - -> 当前实例的类操作对象。 - -### name *- field* - -```kotlin -val name: String -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得当前 `classSet` 的 `Class.getName`。 - -### simpleName *- field* - -```kotlin -val simpleName: String -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得当前 `classSet` 的 `Class.getSimpleName`。 - -### superClass *- method* - -```kotlin -fun superClass(): SuperClass -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 调用父类实例。 - -### field *- method* - -```kotlin -inline fun field(initiate: FieldConditions): FieldFinder.Result.Instance -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 调用当前实例中的变量。 - -### method *- method* - -```kotlin -inline fun method(initiate: MethodConditions): MethodFinder.Result.Instance -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 调用当前实例中的方法。 - -### SuperClass *- class* - -```kotlin -inner class SuperClass internal constructor() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 当前类的父类实例的类操作对象。 - -#### name *- field* - -```kotlin -val name: String -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得当前 `classSet` 中父类的 `Class.getName`。 - -#### simpleName *- field* - -```kotlin -val simpleName: String -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得当前 `classSet` 中父类的 `Class.getSimpleName`。 - -#### field *- method* - -```kotlin -inline fun field(initiate: FieldConditions): FieldFinder.Result.Instance -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 调用父类实例中的变量。 - -#### method *- method* - -```kotlin -inline fun method(initiate: MethodConditions): MethodFinder.Result.Instance -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 调用父类实例中的方法。 \ No newline at end of file diff --git a/docs/api/public/DefinedTypeFactory.md b/docs/api/public/DefinedTypeFactory.md deleted file mode 100644 index 3dcfbfab..00000000 --- a/docs/api/public/DefinedTypeFactory.md +++ /dev/null @@ -1,23 +0,0 @@ -## DefinedTypeFactory *- kt* - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 这是一个内部类型的定义常量类,主要用于反射 API 相关用法的延伸。 - -### VagueType *- field* - -```kotlin -val VagueType: Class<*> -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 得到模糊类型。 \ No newline at end of file diff --git a/docs/api/public/FieldFinder.md b/docs/api/public/FieldFinder.md deleted file mode 100644 index 5591efa8..00000000 --- a/docs/api/public/FieldFinder.md +++ /dev/null @@ -1,871 +0,0 @@ -## FieldFinder *- class* - -```kotlin -class FieldFinder internal constructor(override val hookInstance: YukiMemberHookCreator.MemberHookCreator?, override val classSet: Class<*>?) : MemberBaseFinder -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.2` `修改` - -合并到 `BaseFinder` - -`v1.1.0` `修改` - -合并到 `MemberBaseFinder` - -**功能描述** - -> `Field` 查找类。 - -可通过指定类型查找指定 `Field` 或一组 `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` 类型。 - -可不填写类型。 - -### modifiers *- method* - -```kotlin -fun modifiers(conditions: ModifierConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.0.67` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `修改` - -合并到 `ModifierConditions` - -**功能描述** - -> 设置 `Field` 标识符筛选条件。 - -可不设置筛选条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### order *- method* - -```kotlin -fun order(): IndexTypeCondition -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 顺序筛选字节码的下标。 - -### name *- method* - -```kotlin -fun name(value: String): IndexTypeCondition -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 设置 `Field` 名称。 - -!> 若不填写名称则必须存在一个其它条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### name *- method* - -```kotlin -fun name(conditions: NameConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.0.88` `新增` - -`v1.1.0` `修改` - -合并到 `NameConditions` - -**功能描述** - -> 设置 `Field` 名称条件。 - -!> 若不填写名称则必须存在一个其它条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### type *- method* - -```kotlin -fun type(value: Any): IndexTypeCondition -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 设置 `Field` 类型。 - -!> 可不填写类型。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### superClass *- method* - -```kotlin -fun superClass(isOnlySuperClass: Boolean) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置在 `classSet` 的所有父类中查找当前 `Field`。 - -!> 若当前 `classSet` 的父类较多可能会耗时,API 会自动循环到父类继承是 `Any` 前的最后一个类。 - -### RemedyPlan *- class* - -```kotlin -inner class RemedyPlan internal constructor() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> `Field` 重查找实现类,可累计失败次数直到查找成功。 - -#### field *- method* - -```kotlin -inline fun field(initiate: FieldConditions): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 创建需要重新查找的 `Field`。 - -你可以添加多个备选 `Field`,直到成功为止,若最后依然失败,将停止查找并输出错误日志。 - -#### Result *- class* - -```kotlin -inner class Result internal constructor() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> `RemedyPlan` 结果实现类。 - -##### onFind *- method* - -```kotlin -fun onFind(initiate: HashSet.() -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 当在 `RemedyPlan` 中找到结果时。 - -**功能示例** - -你可以方便地对重查找的 `Field` 实现 `onFind` 方法。 - -> 示例如下 - -```kotlin -field { - // Your code here. -}.onFind { - // Your code here. -} -``` - -### Result *- class* - -```kotlin -inner class Result internal constructor(internal val isNoSuch: Boolean, private val throwable: Throwable?) : BaseResult -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -继承到接口 `BaseResult` - -**功能描述** - -> `Field` 查找结果实现类。 - -#### result *- method* - -```kotlin -inline fun result(initiate: Result.() -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建监听结果事件方法体。 - -**功能示例** - -你可以使用 `lambda` 形式创建 `Result` 类。 - -> 示例如下 - -```kotlin -field { - // Your code here. -}.result { - get(instance).set("something") - get(instance).string() - get(instance).cast() - get().boolean() - all(instance) - give() - giveAll() - onNoSuchField {} -} -``` - -#### get *- method* - -```kotlin -fun get(instance: Any?): Instance -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获得 `Field` 实例处理类。 - -若有多个 `Field` 结果只会返回第一个。 - -**功能示例** - -你可以轻松地得到 `Field` 的实例以及使用它进行设置实例。 - -> 示例如下 - -```kotlin -field { - // Your code here. -}.get(instance).set("something") -``` - -如果你取到的是静态 `Field`,可以不需要设置实例。 - -> 示例如下 - -```kotlin -field { - // Your code here. -}.get().set("something") -``` - -#### all *- method* - -```kotlin -fun all(instance: Any?): ArrayList -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Field` 实例处理类数组。 - -返回全部查询条件匹配的多个 `Field` 实例结果。 - -**功能示例** - -你可以通过此方法来获得当前条件结果中匹配的全部 `Field`,其 `Field` 所在实例用法与 `get` 相同。 - -> 示例如下 - -```kotlin -field { - // Your code here. -}.all(instance).forEach { instance -> - instance.self -} -``` - -#### give *- method* - -```kotlin -fun give(): Field? -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 得到 `Field` 本身。 - -若有多个 Field 结果只会返回第一个。 - -在查询条件找不到任何结果的时候将返回 `null`。 - -#### giveAll *- method* - -```kotlin -fun giveAll(): HashSet -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 得到 `Field` 本身数组。 - -返回全部查询条件匹配的多个 `Field` 实例。 - -在查询条件找不到任何结果的时候将返回空的 `HashSet`。 - -#### wait *- method* - -```kotlin -fun wait(instance: Any?, initiate: Instance.() -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Field` 实例处理类,配合 `RemedyPlan` 使用。 - -若有多个 `Field` 结果只会返回第一个。 - -!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 - -!> 若你没有设置 `remedys` 此方法将不会被回调。 - -#### waitAll *- method* - -```kotlin -fun waitAll(instance: Any?, initiate: ArrayList.() -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Field` 实例处理类数组,配合 `RemedyPlan` 使用。 - -返回全部查询条件匹配的多个 `Field` 实例结果。 - -!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 - -!> 若你没有设置 `remedys` 此方法将不会被回调。 - -#### remedys *- method* - -```kotlin -inline fun remedys(initiate: RemedyPlan.() -> Unit): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 创建 `Field` 重查找功能。 - -**功能示例** - -当你遇到一种 `Field` 可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchField` 捕获异常二次查找 `Field`。 - -若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 - -> 示例如下 - -```kotlin -field { - // Your code here. -}.remedys { - field { - // Your code here. - } - field { - // Your code here. - } -} -``` - -#### onNoSuchField *- method* - -```kotlin -fun onNoSuchField(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 监听找不到 `Field` 时。 - -#### ignored *- method* - -```kotlin -fun ignored(): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 忽略异常并停止打印任何错误日志。 - -若 `isNotIgnoredHookingFailure` 为 `false` 则自动忽略。 - -!> 此时若要监听异常结果,你需要手动实现 `onNoSuchField` 方法。 - -#### ~~ignoredError *- method*~~ - -**变更记录** - -`v1.0.3` `新增` - -`v1.1.0` `作废` - -请转移到新方法 `ignored()` - -#### Instance *- class* - -```kotlin -inner class Instance internal constructor(private val instance: Any?, private val field: Field?) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `field` 参数 - -不再对外公开 `self` 参数 - -**功能描述** - -> `Field` 实例变量处理类。 - -##### ~~self *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `移除` - -请直接使用 `any` 方法得到 `Field` 自身的实例化对象 - -##### current *- method* - -```kotlin -fun current(ignored: Boolean): CurrentClass? -``` - -```kotlin -inline fun current(ignored: Boolean, initiate: CurrentClass.() -> Unit): Any? -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得当前 `Field` 自身 `self` 实例的类操作对象 `CurrentClass`。 - -##### cast *- method* - -```kotlin -fun cast(): T? -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.68` `修改` - -修改 ~~`of`~~ 为 `cast` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` 实例。 - -##### byte *- method* - -```kotlin -fun byte(): Byte? -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 得到当前 `Field` Byte 实例。 - -##### int *- method* - -```kotlin -fun int(): Int -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofInt`~~ 为 `int` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Int 实例。 - -##### long *- method* - -```kotlin -fun long(): Long -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofLong`~~ 为 `long` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Long 实例。 - -##### short *- method* - -```kotlin -fun short(): Short -``` -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofShort`~~ 为 `short` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Short 实例。 - -##### double *- method* - -```kotlin -fun double(): Double -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofDouble`~~ 为 `double` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Double 实例。 - -##### float *- method* - -```kotlin -fun float(): Float -``` -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofFloat`~~ 为 `float` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Float 实例。 - -##### string *- method* - -```kotlin -fun string(): String -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofString`~~ 为 `string` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` String 实例。 - -##### char *- method* - -```kotlin -fun char(): Char -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 得到当前 `Field` Char 实例。 - -##### boolean *- method* - -```kotlin -fun boolean(): Boolean -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofBoolean`~~ 为 `boolean` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Boolean 实例。 - -##### any *- method* - -```kotlin -fun any(): Any? -``` -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`ofAny`~~ 为 `any` - -移动方法到 `Instance` - -**功能描述** - -> 得到当前 `Field` Any 实例。 - -##### array *- method* - -```kotlin -inline fun array(): Array -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 得到当前 `Field` Array 实例。 - -##### list *- method* - -```kotlin -inline fun list(): List -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 得到当前 `Field` List 实例。 - -##### set *- method* - -```kotlin -fun set(any: Any?) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 设置当前 `Field` 实例。 - -##### setTrue *- method* - -```kotlin -fun setTrue() -``` -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 设置当前 `Field` 实例为 `true`。 - -!> 请确保实例对象类型为 `Boolean`。 - -##### setFalse *- method* - -```kotlin -fun setFalse() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 设置当前 `Field` 实例为 `false`。 - -!> 请确保实例对象类型为 `Boolean`。 - -##### setNull *- method* - -```kotlin -fun setNull() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 设置当前 `Field` 实例为 `null`。 \ No newline at end of file diff --git a/docs/api/public/GraphicsTypeFactory.md b/docs/api/public/GraphicsTypeFactory.md deleted file mode 100644 index 5249be18..00000000 --- a/docs/api/public/GraphicsTypeFactory.md +++ /dev/null @@ -1,11 +0,0 @@ -## 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 deleted file mode 100644 index 04c1a82a..00000000 --- a/docs/api/public/HookClass.md +++ /dev/null @@ -1,19 +0,0 @@ -## HookClass *- class* - -```kotlin -class HookClass internal constructor(internal var instance: Class<*>?, internal var name: String, internal var throwable: Throwable?) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -`HookClass` 相关功能不再对外开放 - -**功能描述** - -> 创建一个当前 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 deleted file mode 100644 index b8325461..00000000 --- a/docs/api/public/HookParam.md +++ /dev/null @@ -1,831 +0,0 @@ -## HookParam *- class* - -```kotlin -class HookParam internal constructor(private val creatorInstance: YukiMemberHookCreator, private var param: YukiHookCallback.Param?) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -移动 `HookParamWrapper` 到 `YukiHookCallback.Param` - -`v1.1.0` `修改` - -修正拼写错误的 **creater** 命名到 **creator** - -**功能描述** - -> Hook 方法、构造方法的目标对象实现类。 - -### args *- field* - -```kotlin -val args: Array -``` - -**变更记录** - -在 `v1.0` 添加 - -**功能描述** - -> 获取当前 Hook 对象 `member` 或 `constructor` 的参数对象数组。 - -这里的数组每项类型默认为 `Any`,你可以使用 `args` 方法来实现 `ArgsModifyer.cast` 功能。 - -### ~~firstArgs *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.75` `移除` - -请使用 `args(index = 0)` 或 `args().first()` - -### ~~lastArgs *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.75` `移除` - -请使用 `args().last()` - -### instance *- field* - -```kotlin -val instance: Any -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获取当前 Hook 实例的对象。 - -!> 如果你当前 Hook 的对象是一个静态,那么它将不存在实例的对象。 - -### instanceClass *- field* - -```kotlin -val instanceClass: Class<*> -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获取当前 Hook 实例的类对象。 - -### member *- field* - -```kotlin -val member: Member -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获取当前 Hook 对象的 `Member`。 - -在不确定 `Member` 类型为 `Method` 或 `Constructor` 时可以使用此方法。 - -### 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` 的返回值。 - -### hasThrowable *- field* - -```kotlin -val hasThrowable: Boolean -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 判断是否存在设置过的方法调用抛出异常。 - -### throwable *- field* - -```kotlin -val throwable: Throwable? -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获取设置的方法调用抛出异常。 - -### Throwable.throwToApp *- i-ext-method* - -```kotlin -fun Throwable.throwToApp() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 向 Hook APP 抛出异常。 - -使用 `hasThrowable` 判断当前是否存在被抛出的异常。 - -使用 `throwable` 获取当前设置的方法调用抛出异常。 - -仅会在回调方法的 `MemberHookCreator.beforeHook` or `MemberHookCreator.afterHook` 中生效。 - -!> 设置后会同时执行 `resultNull` 方法并将异常抛出给当前 Hook APP。 - -**功能示例** - -Hook 过程中的异常仅会作用于 (Xposed) 宿主环境,目标 Hook APP 不会受到影响。 - -若想将异常抛给 Hook APP,可以直接使用如下方法。 - -> 示例如下 - -```kotlin -injectMember { - method { - // ... - } - beforeHook { - RuntimeException("Test Exception").throwToApp() - } -} -``` - -!> 向 Hook APP 抛出异常**会对其暴露被 Hook 的事实**,是不安全的,容易被检测,请按实际场景合理使用。 - -### result *- method* - -```kotlin -inline fun result(): T? -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 获取当前 Hook 对象的 `method` 或 `constructor` 的返回值 `T`。 - -### ~~firstArgs *- method*~~ - -**变更记录** - -`v1.0.66` `新增` - -`v1.0.75` `移除` - -### ~~lastArgs *- method*~~ - -**变更记录** - -`v1.0.66` `新增` - -`v1.0.75` `移除` - -### instance *- method* - -```kotlin -inline fun instance(): T -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获取当前 Hook 实例的对象 `T`。 - -**功能示例** - -你可以通过 `instance` 方法轻松使用泛型 `cast` 为目标对象的类型。 - -> 示例如下 - -```kotlin -instance().finish() -``` - -### args *- method* - -```kotlin -fun args(): ArgsIndexCondition -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 获取当前 Hook 对象的 `method` or `constructor` 的参数数组下标实例化类。 - -### args *- method* - -```kotlin -fun args(index: Int): ArgsModifyer -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.75` `修改` - -默认值 `index = 0` 移动到新的使用方法 `args().first()` - -**功能描述** - -> 获取当前 Hook 对象的 `method` 或 `constructor` 的参数实例化对象类。 - -**功能示例** - -你可以通过 `args` 方法修改当前 Hook 实例的方法、构造方法的参数内容。 - -你可以直接使用 `set` 方法设置 `param` 为你的目标实例,接受 `Any` 类型。 - -!> 请确保 `param` 类型为你的目标实例类型。 - -> 示例如下 - -```kotlin -args(index = 0).set("modify the value") -``` - -你可以这样直接设置第一位 `param` 的值。 - -> 示例如下 - -```kotlin -args().first().set("modify the value") -``` - -你还可以直接设置最后一位 `param` 的值。 - -> 示例如下 - -```kotlin -args().last().set("modify the value") -``` - -你还可以使用 `setNull` 方法设置 `param` 为空。 - -> 示例如下 - -```kotlin -args(index = 1).setNull() -``` - -你还可以使用 `setTrue` 方法设置 `param` 为 `true`。 - -!> 请确保 `param` 类型为 `Boolean`。 - -> 示例如下 - -```kotlin -args(index = 1).setTrue() -``` - -你还可以使用 `setFalse` 方法设置 `param` 为 `false`。 - -!> 请确保 `param` 类型为 `Boolean`。 - -> 示例如下 - -```kotlin -args(index = 1).setFalse() -``` - -### callOriginal *- method* - -```kotlin -fun callOriginal(): Any? -``` - -```kotlin -fun callOriginal(): T? -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 执行原始 `Member`。 - -调用自身未进行 Hook 的原始 `Member` 并调用原始参数执行。 - -**功能实例** - -此方法可以 `invoke` 原始未经 Hook 的 `Member` 对象,取决于原始 `Member` 的参数。 - -调用自身原始的方法不会再经过当前 `beforeHook`、`afterHook` 以及 `replaceUnit`、`replaceAny`。 - -比如我们 Hook 的这个方法被这样调用 `test("test value")`,使用此方法会调用其中的 `"test value"` 作为参数。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "test" - param(StringType) - returnType = StringType - } - afterHook { - // <方案1> 不使用泛型,不获取方法执行结果,调用将使用原方法传入的 args 自动传参 - callOriginal() - // <方案2> 使用泛型,已知方法执行结果参数类型进行 cast,假设返回值为 String,失败会返回 null,调用将使用原方法传入的 args 自动传参 - val value = callOriginal() - } -} -``` - -### invokeOriginal *- method* - -```kotlin -fun invokeOriginal(vararg args: Any?): Any? -``` - -```kotlin -fun invokeOriginal(vararg args: Any?): T? -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -不再需要使用 `member.invokeOriginal` 进行调用 - -**功能描述** - -> 执行原始 `Member`。 - -调用自身未进行 Hook 的原始 `Member` 并自定义 `args` 执行。 - -**功能实例** - -此方法可以 `invoke` 原始未经 Hook 的 `Member` 对象,可自定义需要调用的参数内容。 - -调用自身原始的方法不会再经过当前 `beforeHook`、`afterHook` 以及 `replaceUnit`、`replaceAny`。 - -比如我们 Hook 的这个方法被这样调用 `test("test value")`,使用此方法可自定义其中的 `args` 作为参数。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "test" - param(StringType) - returnType = StringType - } - afterHook { - // <方案1> 不使用泛型,不获取方法执行结果 - invokeOriginal("test value") - // <方案2> 使用泛型,已知方法执行结果参数类型进行 cast,假设返回值为 String,失败会返回 null - val value = 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`。 - -### ArgsIndexCondition *- class* - -```kotlin -inner class ArgsIndexCondition internal constructor() -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 对方法参数的数组下标进行实例化类。 - -#### first *- method* - -```kotlin -fun first(): ArgsModifyer -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 获取当前 Hook 对象的 `method` or `constructor` 的参数数组第一位。 - -#### last *- method* - -```kotlin -fun last(): ArgsModifyer -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 获取当前 Hook 对象的 `method` or `constructor` 的参数数组最后一位。 - -### ArgsModifyer *- class* - -```kotlin -inner class ArgsModifyer internal constructor(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。 - -#### any *- method* - -```kotlin -fun any(): Any? -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 得到方法参数的实例对象 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: 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/HookResources.md b/docs/api/public/HookResources.md deleted file mode 100644 index 54e557ed..00000000 --- a/docs/api/public/HookResources.md +++ /dev/null @@ -1,13 +0,0 @@ -## HookResources *- class* - -```kotlin -class HookResources internal constructor(var instance: YukiResources?) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 创建一个当前 Hook 的 `YukiResources` 接管类。 \ No newline at end of file diff --git a/docs/api/public/IYukiHookXposedInit.md b/docs/api/public/IYukiHookXposedInit.md deleted file mode 100644 index 8e8082b7..00000000 --- a/docs/api/public/IYukiHookXposedInit.md +++ /dev/null @@ -1,89 +0,0 @@ -## IYukiHookXposedInit *- interface* - -```kotlin -interface IYukiHookXposedInit -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` `作废` - -作废了 ~~`YukiHookXposedInitProxy`~~ 名称但保留接口 - -转移到 `IYukiHookXposedInit` 新名称 - -**功能描述** - -> 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 的模块装载调用入口方法。 - -### onXposedEvent *- method* - -```kotlin -fun onXposedEvent() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 监听 Xposed 原生装载事件。 - -若你的 Hook 事件中存在需要兼容的原生 Xposed 功能,可在这里实现。 - -请在这里使用 `YukiXposedEvent` 创建回调事件监听。 - -可监听的事件如下: - -`YukiXposedEvent.onInitZygote` - -`YukiXposedEvent.onHandleLoadPackage` - -`YukiXposedEvent.onHandleInitPackageResources` - -!> 此接口仅供监听和实现原生 Xposed API 的功能,请不要在这里操作 `YukiHookAPI`。 - -## ~~YukiHookXposedInitProxy *- interface*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `作废` - -请转移到 `IYukiHookXposedInit` \ No newline at end of file diff --git a/docs/api/public/InjectYukiHookWithXposed.md b/docs/api/public/InjectYukiHookWithXposed.md deleted file mode 100644 index 9b479c83..00000000 --- a/docs/api/public/InjectYukiHookWithXposed.md +++ /dev/null @@ -1,30 +0,0 @@ -## InjectYukiHookWithXposed *- annotation* - -```kotlin -annotation class InjectYukiHookWithXposed( - val sourcePath: String, - val modulePackageName: String, - val entryClassName: String, - val isUsingResourcesHook: Boolean -) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -新增 `entryClassName` 参数 - -`v1.0.92` `修改` - -新增 `isUsingResourcesHook` 参数 - -**功能描述** - -> 标识 `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 deleted file mode 100644 index 8fa6d93f..00000000 --- a/docs/api/public/LoggerFactory.md +++ /dev/null @@ -1,171 +0,0 @@ -## LoggerFactory *- kt* - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 这是 `YukiHookAPI` 的日志封装类,可实现同时向 `Logcat` 和 `XposedBridge.log` 打印日志的功能。 - -### LoggerType *- class* - -```kotlin -enum class LoggerType -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 需要打印的日志类型。 - -决定于模块与 (Xposed) 宿主环境使用的打印方式。 - -#### LOGD *- enum* - -```kotlin -LOGD -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 仅使用 `android.util.Log`。 - -#### XPOSEDBRIDGE *- enum* - -```kotlin -XPOSEDBRIDGE -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 仅使用 `XposedBridge.log`。 - -!> 只能在 (Xposed) 宿主环境中使用,模块环境将不生效。 - -#### SCOPE *- enum* - -```kotlin -SCOPE -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 分区使用。 - -(Xposed) 宿主环境仅使用 `XPOSEDBRIDGE`。 - -模块环境仅使用 `LOGD`。 - -#### BOTH *- enum* - -```kotlin -BOTH -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 同时使用。 - -(Xposed) 宿主环境使用 `LOGD` 与 `XPOSEDBRIDGE`。 - -模块环境仅使用 `LOGD`。 - -### loggerD *- method* - -```kotlin -fun loggerD(tag: String, msg: String, type: LoggerType) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `type` 参数 - -**功能描述** - -> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `D`。 - -`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 - -### loggerI *- method* - -```kotlin -fun loggerI(tag: String, msg: String, type: LoggerType) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `type` 参数 - -**功能描述** - -> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `I`。 - -`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 - -### loggerW *- method* - -```kotlin -fun loggerW(tag: String, msg: String, type: LoggerType) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `type` 参数 - -**功能描述** - -> 向 `Logcat` 和 `XposedBridge` 打印日志,级别 `W`。 - -`tag` 的默认参数为 `YukiHookAPI.Configs.debugTag`,你可以进行自定义。 - -### loggerE *- method* - -```kotlin -fun loggerE(tag: String, msg: String, e: Throwable?, type: LoggerType) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `type` 参数 - -**功能描述** - -> 向 `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 deleted file mode 100644 index 8fa95b42..00000000 --- a/docs/api/public/MethodFinder.md +++ /dev/null @@ -1,1004 +0,0 @@ -## MethodFinder *- class* - -```kotlin -class MethodFinder internal constructor(override val hookInstance: YukiMemberHookCreator.MemberHookCreator?, override val classSet: Class<*>) : MemberBaseFinder -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.2` `修改` - -合并到 `BaseFinder` - -`v1.1.0` `修改` - -合并到 `MemberBaseFinder` - -**功能描述** - -> `Method` 查找类。 - -可通过指定类型查找指定 `Method` 或一组 `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` 返回值,可不填写返回值。 - -### modifiers *- method* - -```kotlin -fun modifiers(conditions: ModifierConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.0.67` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `修改` - -合并到 `ModifierConditions` - -**功能描述** - -> 设置 `Method` 标识符筛选条件。 - -可不设置筛选条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### emptyParam *- method* - -```kotlin -fun emptyParam(): IndexTypeCondition -``` - -**变更记录** - -`v1.0.75` `新增` - -**功能描述** - -> 设置 `Method` 空参数、无参数。 - -### param *- method* - -```kotlin -fun param(vararg paramType: Any): IndexTypeCondition -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 设置 `Method` 参数。 - -如果同时使用了 `paramCount` 则 `paramType` 的数量必须与 `paramCount` 完全匹配。 - -如果 `Method` 中存在一些无意义又很长的类型,你可以使用 `VagueType` 来替代它。 - -!> 无参 `Method` 请使用 `emptyParam` 设置查询条件。 - -!> 有参 `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` 名称。 - -!> 若不填写名称则必须存在一个其它条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### name *- method* - -```kotlin -fun name(conditions: NameConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.0.88` `新增` - -`v1.1.0` `修改` - -合并到 `NameConditions` - -**功能描述** - -> 设置 `Method` 名称条件。 - -!> 若不填写名称则必须存在一个其它条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### paramCount *- method* - -```kotlin -fun paramCount(num: Int): IndexTypeCondition -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 设置 `Method` 参数个数。 - -你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数。 - -若参数个数小于零则忽略并使用 `param`。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### paramCount *- method* - -```kotlin -fun paramCount(numRange: IntRange): IndexTypeCondition -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置 `Method` 参数个数范围。 - -你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数范围。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### paramCount *- method* - -```kotlin -fun paramCount(conditions: CountConditions): IndexTypeCondition -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置 `Method` 参数个数条件。 - -你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数条件。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### returnType *- method* - -```kotlin -fun returnType(value: Any): IndexTypeCondition -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 设置 `Method` 返回值。 - -可不填写返回值。 - -!> 存在多个 `IndexTypeCondition` 时除了 `order` 只会生效最后一个。 - -### superClass *- method* - -```kotlin -fun superClass(isOnlySuperClass: Boolean) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置在 `classSet` 的所有父类中查找当前 `Method`。 - -!> 若当前 `classSet` 的父类较多可能会耗时,API 会自动循环到父类继承是 `Any` 前的最后一个类。 - -### RemedyPlan *- class* - -```kotlin -inner class RemedyPlan internal constructor() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> `Method` 重查找实现类,可累计失败次数直到查找成功。 - -#### method *- method* - -```kotlin -inline fun method(initiate: MethodConditions): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建需要重新查找的 `Method`。 - -你可以添加多个备选 `Method`,直到成功为止,若最后依然失败,将停止查找并输出错误日志。 - -#### Result *- class* - -```kotlin -inner class Result internal constructor() -``` - -**变更记录** - -`v1.0.1` `新增` - -**功能描述** - -> `RemedyPlan` 结果实现类。 - -##### onFind *- method* - -```kotlin -fun onFind(initiate: HashSet.() -> Unit) -``` - -**变更记录** - -`v1.0.1` `新增` - -`v1.1.0` `修改` - -`initiate` 参数 `Method` 变为 `HashSet` - -**功能描述** - -> 当在 `RemedyPlan` 中找到结果时。 - -**功能示例** - -你可以方便地对重查找的 `Method` 实现 `onFind` 方法。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.onFind { - // Your code here. -} -``` - -### Process *- class* - -```kotlin -inner class Process internal constructor(internal val isNoSuch: Boolean, internal val throwable: Throwable?) : BaseResult -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> `Method` 查找结果处理类,为 `hookInstance` 提供。 - -#### result *- method* - -```kotlin -inline fun result(initiate: Process.() -> Unit): Process -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 创建监听结果事件方法体。 - -**功能示例** - -你可以使用 `lambda` 形式创建 `Result` 类。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.result { - all() - remedys {} - onNoSuchMethod {} -} -``` - -#### all *- method* - -```kotlin -fun all(): Process -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置全部查询条件匹配的多个 `Method` 实例结果到 `hookInstance`。 - -#### remedys *- method* - -```kotlin -inline fun remedys(initiate: RemedyPlan.() -> Unit): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 创建 `Method` 重查找功能。 - -**功能示例** - -当你遇到一种 `Method` 可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchMethod` 捕获异常二次查找 `Method`。 - -若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.remedys { - method { - // Your code here. - } - method { - // Your code here. - } -} -``` - -#### onNoSuchMethod *- method* - -```kotlin -inline fun onNoSuchMethod(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 监听找不到 `Method` 时。 - -只会返回第一次的错误信息,不会返回 `RemedyPlan` 的错误信息。 - -### Result *- class* - -```kotlin -inner class Result internal constructor(internal val isNoSuch: Boolean, private val throwable: Throwable?) : BaseResult -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -继承到接口 `BaseResult` - -**功能描述** - -> `Method` 查找结果实现类。 - -#### result *- method* - -```kotlin -inline fun result(initiate: Result.() -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建监听结果事件方法体。 - -**功能示例** - -你可以使用 `lambda` 形式创建 `Result` 类。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.result { - get(instance).call() - all(instance) - remedys {} - onNoSuchMethod {} -} -``` - -#### get *- method* - -```kotlin -fun get(instance: Any?): Instance -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 获得 `Method` 实例处理类。 - -若有多个 `Method` 结果只会返回第一个。 - -!> 若你设置了 `remedys` 请使用 `wait` 回调结果方法。 - -**功能示例** - -你可以通过获得方法所在实例来执行 `Method`。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.get(instance).call() -``` - -若当前为静态方法,你可以不设置实例。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.get().call() -``` - -#### all *- method* - -```kotlin -fun all(instance: Any?): ArrayList -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Method` 实例处理类数组。 - -返回全部查询条件匹配的多个 `Method` 实例结果。 - -**功能示例** - -你可以通过此方法来获得当前条件结果中匹配的全部 `Method`,其方法所在实例用法与 `get` 相同。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.all(instance).forEach { instance -> - instance.call(...) -} -``` - -#### give *- method* - -```kotlin -fun give(): Method? -``` - -**变更记录** - -`v1.0.67` `新增` - -**功能描述** - -> 得到 `Method` 本身。 - -若有多个 `Method` 结果只会返回第一个。 - -在查询条件找不到任何结果的时候将返回 `null`。 - -#### giveAll *- method* - -```kotlin -fun giveAll(): HashSet -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 得到 `Method` 本身数组。 - -返回全部查询条件匹配的多个 `Method` 实例。 - -在查询条件找不到任何结果的时候将返回空的 `HashSet`。 - -#### wait *- method* - -```kotlin -fun wait(instance: Any?, initiate: Instance.() -> Unit) -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 获得 `Method` 实例处理类,配合 `RemedyPlan` 使用。 - -若有多个 `Method` 结果只会返回第一个。 - -!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 - -!> 若你没有设置 `remedys` 此方法将不会被回调。 - -#### waitAll *- method* - -```kotlin -fun waitAll(instance: Any?, initiate: ArrayList.() -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获得 `Method` 实例处理类数组,配合 `RemedyPlan` 使用。 - -返回全部查询条件匹配的多个 `Method` 实例结果。 - -!> 若你设置了 `remedys` 必须使用此方法才能获得结果。 - -!> 若你没有设置 `remedys` 此方法将不会被回调。 - -#### remedys *- method* - -```kotlin -inline fun remedys(initiate: RemedyPlan.() -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建 `Method` 重查找功能。 - -**功能示例** - -当你遇到一种 `Method` 可能存在不同形式的存在时,可以使用 `RemedyPlan` 重新查找它,而没有必要使用 `onNoSuchMethod` 捕获异常二次查找 `Method`。 - -若第一次查找失败了,你还可以在这里继续添加此方法体直到成功为止。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.remedys { - method { - // Your code here. - } - method { - // Your code here. - } -} -``` - -#### onNoSuchMethod *- method* - -```kotlin -inline fun onNoSuchMethod(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 监听找不到 `Method` 时。 - -只会返回第一次的错误信息,不会返回 `RemedyPlan` 的错误信息。 - -#### ignored *- method* - -```kotlin -fun ignored(): Result -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 忽略异常并停止打印任何错误日志。 - -若 `isNotIgnoredHookingFailure` 为 `false` 则自动忽略。 - -!> 此时若要监听异常结果,你需要手动实现 `onNoSuchMethod` 方法。 - -#### ~~ignoredError *- method*~~ - -**变更记录** - -`v1.0.3` `新增` - -`v1.1.0` `作废` - -请转移到新方法 `ignored()` - -#### Instance *- class* - -```kotlin -inner class Instance internal constructor(private val instance: Any?, private val method: Method?) -``` - -**变更记录** - -`v1.0.2` `新增` - -`v1.1.0` `修改` - -新增 `method` 参数 - -**功能描述** - -> `Method` 实例处理类。 - -##### original *- method* - -```kotlin -fun original(): Instance -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 标识需要调用当前 `Method` 未经 Hook 的原始 `Method`。 - -若当前 `Method` 并未 Hook 则会使用原始的 `Method.invoke` 方法调用。 - -!> 你只能在 (Xposed) 宿主环境中使用此功能。 - -##### call *- method* - -```kotlin -fun call(vararg param: Any?): Any? -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 执行 `Method`,不指定返回值类型。 - -##### invoke *- method* - -```kotlin -fun invoke(vararg param: Any?): T? -``` - -**变更记录** - -`v1.0.2` `新增` - -**功能描述** - -> 执行 `Method`,指定 `T` 返回值类型。 - -##### byte *- method* - -```kotlin -fun byte(vararg param: Any?): Byte? -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 执行 `Method`,指定 Byte 返回值类型。 - -##### int *- method* - -```kotlin -fun int(vararg param: Any?): Int -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callInt`~~ 为 `int` - -**功能描述** - -> 执行 `Method`,指定 Int 返回值类型。 - -##### long *- method* - -```kotlin -fun long(vararg param: Any?): Long -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callLong`~~ 为 `long` - -**功能描述** - -> 执行 `Method`,指定 Long 返回值类型。 - -##### short *- method* - -```kotlin -fun short(vararg param: Any?): Short -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callShort`~~ 为 `short` - -**功能描述** - -> 执行 `Method`,指定 Short 返回值类型。 - -##### double *- method* - -```kotlin -fun double(vararg param: Any?): Double -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callDouble`~~ 为 `double` - -**功能描述** - -> 执行 `Method`,指定 Double 返回值类型。 - -##### float *- method* - -```kotlin -fun float(vararg param: Any?): Float -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callFloat`~~ 为 `float` - -**功能描述** - -> 执行 `Method`,指定 Float 返回值类型。 - -##### string *- method* - -```kotlin -fun string(vararg param: Any?): String -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callString`~~ 为 `string` - -**功能描述** - -> 执行 `Method`,指定 String 返回值类型。 - -##### char *- method* - -```kotlin -fun char(vararg param: Any?): Char -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 执行 `Method`,指定 Char 返回值类型。 - -##### boolean *- method* - -```kotlin -fun boolean(vararg param: Any?): Boolean -``` - -**变更记录** - -`v1.0.65` `新增` - -`v1.0.68` `修改` - -修改 ~~`callBoolean`~~ 为 `boolean` - -**功能描述** - -> 执行 `Method`,指定 Boolean 返回值类型。 - -#### array *- method* - -```kotlin -inline fun array(vararg param: Any?): Array -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 执行 `Method`,指定 Array 返回值类型。 - -#### list *- method* - -```kotlin -inline fun list(vararg param: Any?): List -``` - -**变更记录** - -`v1.0.68` `新增` - -**功能描述** - -> 执行 `Method`,指定 List 返回值类型。 \ No newline at end of file diff --git a/docs/api/public/ModifierRules.md b/docs/api/public/ModifierRules.md deleted file mode 100644 index 76e1e7af..00000000 --- a/docs/api/public/ModifierRules.md +++ /dev/null @@ -1,23 +0,0 @@ -## ModifierRules *- class* - -```kotlin -class ModifierRules private constructor() -``` - -**变更记录** - -`v1.0.67` `新增` - -`v1.1.0` `修改` - -新增 `Class` 的描述符判断 - -作为 lambda 整体判断条件使用 - -移动到 base 包名 - -私有化构造方法 - -**功能描述** - -!> 新的功能已转移到新文档,这里仅保留文字说明记录。 \ No newline at end of file diff --git a/docs/api/public/ModuleAppActivity.md b/docs/api/public/ModuleAppActivity.md deleted file mode 100644 index c9412add..00000000 --- a/docs/api/public/ModuleAppActivity.md +++ /dev/null @@ -1,17 +0,0 @@ -## ModuleAppActivity *- class* - -```kotlin -open class ModuleAppActivity : Activity() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 代理 `Activity`。 - -继承于此类的 `Activity` 可以同时在宿主与模块中启动。 - -在 (Xposed) 宿主环境需要在宿主启动时调用 `Context.registerModuleAppActivities` 进行注册。 \ No newline at end of file diff --git a/docs/api/public/ModuleAppCompatActivity.md b/docs/api/public/ModuleAppCompatActivity.md deleted file mode 100644 index 15200f5d..00000000 --- a/docs/api/public/ModuleAppCompatActivity.md +++ /dev/null @@ -1,33 +0,0 @@ -## ModuleAppCompatActivity *- class* - -```kotlin -open class ModuleAppCompatActivity : AppCompatActivity() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 代理 `AppCompatActivity`。 - -继承于此类的 `Activity` 可以同时在宿主与模块中启动。 - -在 (Xposed) 宿主环境需要在宿主启动时调用 `Context.registerModuleAppActivities` 进行注册。 - -在 (Xposed) 宿主环境需要重写 `moduleTheme` 设置 AppCompat 主题,否则会无法启动。 - -### moduleTheme *- field* - -```kotlin -open val moduleTheme: Int -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置当前代理的 `Activity` 主题。 \ No newline at end of file diff --git a/docs/api/public/ModuleApplication.md b/docs/api/public/ModuleApplication.md deleted file mode 100644 index c80a06f3..00000000 --- a/docs/api/public/ModuleApplication.md +++ /dev/null @@ -1,80 +0,0 @@ -## ModuleApplication *- class* - -```kotlin -open class ModuleApplication: Application() -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 这是对使用 `YukiHookAPI` Xposed 模块实现中的一个扩展功能。 - -在你的 Xposed 模块的 `Application` 中继承此类。 - -或在 `AndroidManifest.xml` 的 `application` 标签中指定此类。 - -目前可实现功能如下 - -- 全局共享模块中静态的 `appContext` - -- 在模块与宿主中装载 `YukiHookAPI.Config` 以确保 `YukiHookAPI.Configs.debugTag` 不需要重复定义 - -- 在模块与宿主中使用 `YukiHookDataChannel` 进行通讯 - -- 在模块中使用系统隐藏 API,核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection) - -- 在模块中使用 `YukiHookAPI.Status.isTaiChiModuleActive` 判断太极、无极激活状态 - -**功能示例** - -将此类继承到你的自定义 `Application` 上。 - -> 示例如下 - -```kotlin -package com.demo - -class MyApplication: ModuleApplication() { - - override fun onCreate() { - super.onCreate() - } -} -``` - -在 `AndroidManifest.xml` 的 `application` 标签中指定自定义的 `Application`。 - -> 示例如下 - -```xml - -``` - -如果你不需要自定义 `Application` 可以直接将 `ModuleApplication` 设置到 `AndroidManifest.xml` 的 `application` 标签中。 - -> 示例如下 - -```xml - -``` - -### appContext *- field* - -```kotlin -val appContext: ModuleApplication -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 获取全局静态 `Application` 实例。 \ No newline at end of file diff --git a/docs/api/public/ModuleContextThemeWrapper.md b/docs/api/public/ModuleContextThemeWrapper.md deleted file mode 100644 index a0f0e8ac..00000000 --- a/docs/api/public/ModuleContextThemeWrapper.md +++ /dev/null @@ -1,31 +0,0 @@ -## ModuleContextThemeWrapper *- class* - -```kotlin -class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: Int, configuration: Configuration?) : ContextThemeWrapper -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 代理 `ContextThemeWrapper`。 - -通过包装,你可以轻松在 (Xposed) 宿主环境使用来自模块的主题资源。 - -### applyConfiguration *- method* - -```kotlin -fun applyConfiguration(initiate: Configuration.() -> Unit): ModuleContextThemeWrapper -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置当前 `ModuleContextThemeWrapper` 的 `Configuration`。 - -设置后会自动调用 `Resources.updateConfiguration`。 \ No newline at end of file diff --git a/docs/api/public/ModulePreferenceFragment.md b/docs/api/public/ModulePreferenceFragment.md deleted file mode 100644 index 4da19a49..00000000 --- a/docs/api/public/ModulePreferenceFragment.md +++ /dev/null @@ -1,81 +0,0 @@ -## ModulePreferenceFragment *- class* - -```kotlin -abstract class ModulePreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener -``` - -**变更记录** - -`v1.0.78` `新增` - -**功能描述** - -> 这是对使用 `YukiHookAPI` Xposed 模块实现中的一个扩展功能。 - -此类接管了 `PreferenceFragmentCompat` 并对其实现了 Sp 存储在 Xposed 模块中的全局可读可写。 - -在你使用 `PreferenceFragmentCompat` 的实例中,将继承对象换成此类。 - -然后请将重写方法由 `onCreatePreferences` 替换为 `onCreatePreferencesInModuleApp` 即可。 - -**功能示例** - -使用 `ModulePreferenceFragment` 创建一个 `PreferenceFragmentCompat` 对象。 - -> 示例如下 - -```kotlin -class SettingsFragment : ModulePreferenceFragment() { - - override fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.settings_preferences, rootKey) - // Your code here. - } -} -``` - -其余用法与 `PreferenceFragmentCompat` 保持一致。 - -### onCreatePreferencesInModuleApp *- method* - -```kotlin -abstract fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?) -``` - -**变更记录** - -`v1.0.78` `新增` - -**功能描述** - -> 对接原始方法 `onCreatePreferences`。 - -### onSharedPreferenceChanged *- method* - -```kotlin -override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) -``` - -**变更记录** - -`v1.0.78` `新增` - -**功能描述** - -> 实现了 `SharedPreferences.OnSharedPreferenceChangeListener` 的原生监听功能。 - -**功能示例** - -!> 在使用 `onSharedPreferenceChanged` 时请注意保留 super 方法。 - -> 示例如下 - -```kotlin -class SettingsFragment : ModulePreferenceFragment() { - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { - super.onSharedPreferenceChanged(sharedPreferences, key) - // Your code here. - } -} -``` \ No newline at end of file diff --git a/docs/api/public/NameRules.md b/docs/api/public/NameRules.md deleted file mode 100644 index c68a601d..00000000 --- a/docs/api/public/NameRules.md +++ /dev/null @@ -1,23 +0,0 @@ -## NameRules *- class* - -```kotlin -class NameRules private constructor() -``` - -**变更记录** - -`v1.0.88` `新增` - -`v1.1.0` `修改` - -`NameConditions` 更名为 `NameRules` - -作为 lambda 整体判断条件使用 - -移动到 base 包名 - -私有化构造方法 - -**功能描述** - -!> 新的功能已转移到新文档,这里仅保留文字说明记录。 \ No newline at end of file diff --git a/docs/api/public/PackageParam.md b/docs/api/public/PackageParam.md deleted file mode 100644 index 37e77c98..00000000 --- a/docs/api/public/PackageParam.md +++ /dev/null @@ -1,890 +0,0 @@ -## PackageParam *- class* - -```kotlin -open class PackageParam internal constructor(internal 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`。 - -### appUserId *- field* - -```kotlin -val appUserId: Int -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获取当前 Hook APP 的用户 ID。 - -机主为 `0`,应用双开 (分身) 或工作资料因系统环境不同 ID 也各不相同。 - -### appContext *- field* - -```kotlin -val appContext: Application? -``` - -**变更记录** - -`v1.0.72` `新增` - -`v1.1.0` `修改` - -加入可空类型 (空安全) - -**功能描述** - -> 获取当前 Hook APP 的 `Application`。 - -!> 首次装载可能是空的,请延迟一段时间再获取或通过设置 `onAppLifecycle` 监听来完成。 - -### appResources *- field* - -```kotlin -val appResources:Resources? -``` - -**变更记录** - -`v1.0.80` `新增` - -`v1.1.0` `修改` - -加入可空类型 (空安全) - -**功能描述** - -> 获取当前 Hook APP 的 Resources。 - -!> 你只能在 `HookResources.hook` 方法体内或 `appContext` 装载完毕时进行调用。 - -### systemContext *- field* - -```kotlin -val systemContext: Context -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获取当前系统框架的 `Context`。 - -### 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`。 - -### moduleAppFilePath *- field* - -```kotlin -val moduleAppFilePath: String -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获取当前 Xposed 模块自身 APK 文件路径。 - -!> 作为 Hook API 装载时无法使用,会获取到空字符串。 - -### moduleAppResources *- field* - -```kotlin -val moduleAppResources: YukiModuleResources -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获取当前 Xposed 模块自身 `Resources`。 - -!> 作为 Hook API 或不支持的 Hook Framework 装载时无法使用,会抛出异常。 - -### prefs *- field* - -```kotlin -val prefs: YukiHookModulePrefs -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获得当前使用的存取数据对象缓存实例。 - -!> 作为 Hook API 装载时无法使用,会抛出异常。 - -### prefs *- method* - -```kotlin -fun prefs(name: String): YukiHookModulePrefs -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 获得当前使用的存取数据对象缓存实例。 - -你可以通过 `name` 来自定义 Sp 存储的名称。 - -!> 作为 Hook API 装载时无法使用,会抛出异常。 - -### dataChannel *- field* - -```kotlin -val dataChannel: YukiHookDataChannel.NameSpace -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 获得当前使用的数据通讯桥命名空间对象。 - -!> 作为 Hook API 装载时无法使用,会抛出异常。 - -### resources *- method* - -```kotlin -fun resources(): HookResources -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获得当前 Hook APP 的 `YukiResources` 对象。 - -请调用 `HookResources.hook` 方法开始 Hook。 - -### refreshModuleAppResources *- method* - -```kotlin -fun refreshModuleAppResources() -``` - -**变更记录** - -`v1.0.87` `新增` - -**功能描述** - -> 刷新当前 Xposed 模块自身 `Resources`。 - -### onAppLifecycle *- method* - -```kotlin -inline fun onAppLifecycle(initiate: AppLifecycle.() -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 生命周期装载事件。 - -!> 在 `loadZygote` 中不会被装载,仅会在 `loadSystem`、`loadApp` 中装载。 - -!> 作为 Hook API 装载时请使用原生的 `Application` 实现生命周期监听。 - -### loadApp *- method* - -```kotlin -inline fun loadApp(name: String, initiate: PackageParam.() -> Unit) -``` - -```kotlin -fun loadApp(name: String, hooker: YukiBaseHooker) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 装载并 Hook 指定包名的 APP。 - -`name` 为 APP 的包名,后方的两个参数一个可作为 `lambda` 方法体使用,一个可以直接装载子 Hooker。 - -装载并 Hook 指定、全部包名的 APP。 - -若要装载 APP Zygote 事件,请使用 `loadZygote`。 - -若要 Hook 系统框架,请使用 `loadSystem`。 - -**功能示例** - -你可以使用 `loadApp` 的 `lambda` 方法体形式或直接装载一个 Hooker。 - -> 示例如下 - -```kotlin -// 使用 lambda -loadApp(name = "com.example.test") { - // Your code here. -} -// 使用 Hooker -loadApp(name = "com.example.test", CustomHooker) -``` - -若不指定 `name` 参数,则此方法体默认会过滤当前系统中全部可被 Hook 的 APP。 - -> 示例如下 - -```kotlin -// 使用 lambda -loadApp { - // Your code here. -} -// 使用 Hooker -loadApp(hooker = CustomHooker) -``` - -### loadZygote *- method* - -```kotlin -inline fun loadZygote(initiate: PackageParam.() -> Unit) -``` - -```kotlin -fun loadZygote(hooker: YukiBaseHooker) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 装载 APP Zygote 事件。 - -方法中的两个参数一个可作为 `lambda` 方法体使用,一个可以直接装载子 Hooker。 - -### loadSystem *- method* - -```kotlin -inline fun loadSystem(initiate: PackageParam.() -> Unit) -``` - -```kotlin -fun loadSystem(hooker: YukiBaseHooker) -``` - -**变更记录** - -`v1.0.82` `新增` - -**功能描述** - -> 装载并 Hook 系统框架。 - -方法中的两个参数一个可作为 `lambda` 方法体使用,一个可以直接装载子 Hooker。 - -### withProcess *- method* - -```kotlin -inline 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。 - -### ~~String+VariousClass.clazz *- i-ext-field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `作废` - -请转移到 `toClass(...)` 方法 - -### ~~String.hasClass *- i-ext-field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `作废` - -请转移到 `hasClass(...)` 方法 - -### String+VariousClass.toClass *- i-ext-method* - -```kotlin -fun String.toClass(loader: ClassLoader?): Class<*> -``` - -```kotlin -fun VariousClass.toClass(loader: ClassLoader?): Class<*> -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 通过字符串类名、`VariousClass` 转换为 `loader` 中的实体类。 - -默认使用当前 `appClassLoader` 装载目标 `Class`。 - -**功能示例** - -你可以轻松地将 `String` 类型的 `Class` 包名转为 `Class` 实例。 - -> 示例如下 - -```kotlin -"com.example.demo.DemoClass".toClass() -``` - -你还可以向 `loader` 参数传入你自定义的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -"com.example.demo.DemoClass".toClass(customClassLoader) -``` - -你还可以创建一个 `VariousClass`,并转换为实体类。 - -`VariousClass` 会枚举所有设置的 `Class` 并最终获得第一个存在的 `Class`。 - -> 示例如下 - -```kotlin -VariousClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2").toClass() -``` - -同样地,你还可以向 `loader` 参数传入你自定义的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -VariousClass("com.example.demo.DemoClass1", "com.example.demo.DemoClass2").toClass(customClassLoader) -``` - -### String.hasClass *- i-ext-method* - -```kotlin -fun String.hasClass(loader: ClassLoader?): Boolean -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 通过字符串类名查找是否存在。 - -默认使用当前 `appClassLoader` 装载目标 `Class`。 - -**功能示例** - -你可以轻松的使用此方法判断字符串中的类是否存在。 - -> 示例如下 - -```kotlin -if("com.example.demo.DemoClass".hasClass()) { - // Your code here. -} -``` - -你还可以自定义其中的 `loader` 参数,默认为 `appClassLoader`。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -if("com.example.demo.DemoClass".hasClass(customClassLoader)) { - // Your code here. -} -``` - -### findClass *- method* - -```kotlin -fun findClass(name: String, loader: ClassLoader?): HookClass -``` - -```kotlin -fun findClass(vararg name: String, loader: ClassLoader?): VariousClass -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -移除了 ~~`findClass(various: VariousClass)`~~ 方法 - -`v1.1.0` `修改` - -新增 `loader` 参数 - -**功能描述** - -> 通过完整包名+名称查找需要被 Hook 的 `Class`。 - -!> 使用此方法会得到一个 `HookClass` 仅用于 Hook,若想查找 `Class` 请使用 `toClass` 功能。 - -**功能示例** - -你可以使用三种方式查找你需要 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") -``` - -若你当前需要查找的 `Class` 不属于 `appClassLoader`,你可以使用 `loader` 参数指定你要装载的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val outsideLoader: ClassLoader? = ... // 假设这就是你的 ClassLoader -findClass(name = "com.example.demo.OutsideClass", loader = outsideLoader) -``` - -同样地,在不确定多个版本的 `Class` 以及不同名称时,也可以使用 `loader` 参数指定你要装载的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val outsideLoader: ClassLoader? = ... // 假设这就是你的 ClassLoader -findClass("com.example.demo.OutsideClass1", "com.example.demo.OutsideClass2", "com.example.demo.OutsideClass3", loader = outsideLoader) -``` - -### String+Class+VariousClass+HookClass.hook *- i-ext-method* - -```kotlin -inline fun String.hook(initiate: YukiMemberHookCreator.() -> Unit): YukiMemberHookCreator.Result -``` - -```kotlin -inline fun Class<*>.hook(isForceUseAbsolute: Boolean, initiate: YukiMemberHookCreator.() -> Unit): YukiMemberHookCreator.Result -``` - -```kotlin -inline fun VariousClass.hook(initiate: YukiMemberHookCreator.() -> Unit): YukiMemberHookCreator.Result -``` - -```kotlin -inline fun HookClass.hook(initiate: YukiMemberHookCreator.() -> Unit): YukiMemberHookCreator.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -新增 `VariousClass` 的直接调用 `hook` 方法 - -`v1.0.2` `修改` - -新增 `String` 的直接调用 `hook` 方法 - -`v1.0.3` `修改` - -新增 `YukiMemberHookCreator.Result` 返回值 - -`v1.0.70` `修改` - -新增 `isUseAppClassLoader` 参数 - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `修改` - -移除了 ~~`isUseAppClassLoader`~~ 参数 - -添加了 `isForceUseAbsolute` 参数到 `Class.hook` 方法 - -**功能描述** - -> 这是一切 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` 实例进行创建。 - -默认情况下 API 会将 `Class` 实例转换为类名并绑定到 `appClassLoader`,若失败,则会使用原始 `Class` 实例直接进行 Hook。 - -> 示例如下 - -```kotlin -Stub::class.java.hook { - // Your code here. -} -``` - -若当前 `Class` 不在 `appClassLoader` 且自动匹配无法找到该 `Class`,请启用 `isForceUseAbsolute`。 - -> 示例如下 - -```kotlin -YourClass::class.java.hook(isForceUseAbsolute = true) { - // 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. -} -``` - -### HookResources.hook *- i-ext-method* - -```kotlin -inline fun HookResources.hook(initiate: YukiResourcesHookCreator.() -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> Hook APP 的 Resources。 - -!> 请注意你需要确保当前 Hook Framework 支持且 `InjectYukiHookWithXposed.isUsingResourcesHook` 已启用。 - -**功能示例** - -Resources Hook 为固定用法,获取 `resources` 对象,然后调用 `hook` 方法开始 Hook。 - -> 示例如下 - -```kotlin -resources().hook { - // Your code here. -} -``` - -!> 这是固定用法,为了防止发生问题,你不可手动实现任何 `HookResources` 实例执行 `hook` 调用。 - -将 Resources 的 Hook 设置为这样是为了与 `findClass(...).hook` 做到统一,使得调用起来逻辑不会混乱。 - -### AppLifecycle *- class* - -```kotlin -inner class AppLifecycle internal constructor() -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 当前 Hook APP 的生命周期实例处理类。 - -#### attachBaseContext *- method* - -```kotlin -fun attachBaseContext(result: (baseContext: Context, hasCalledSuper: Boolean) -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 装载 `Application.attachBaseContext`。 - -#### onCreate *- method* - -```kotlin -fun onCreate(initiate: Application.() -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 装载 `Application.onCreate`。 - -#### onTerminate *- method* - -```kotlin -fun onTerminate(initiate: Application.() -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 装载 `Application.onTerminate`。 - -#### onLowMemory *- method* - -```kotlin -fun onLowMemory(initiate: Application.() -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 装载 `Application.onLowMemory`。 - -#### onTrimMemory *- method* - -```kotlin -fun onTrimMemory(result: (self: Application, level: Int) -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 装载 `Application.onTrimMemory`。 - -#### onConfigurationChanged *- method* - -```kotlin -fun onConfigurationChanged(result: (self: Application, config: Configuration) -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 监听当前 Hook APP 装载 `Application.onConfigurationChanged`。 - -#### registerReceiver *- method* - -```kotlin -fun registerReceiver(vararg action: String, result: (context: Context, intent: Intent) -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 注册系统广播监听。 \ No newline at end of file diff --git a/docs/api/public/PrefsData.md b/docs/api/public/PrefsData.md deleted file mode 100644 index 3a990c83..00000000 --- a/docs/api/public/PrefsData.md +++ /dev/null @@ -1,59 +0,0 @@ -## 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 deleted file mode 100644 index 970b3b7b..00000000 --- a/docs/api/public/ReflectionFactory.md +++ /dev/null @@ -1,649 +0,0 @@ -## ReflectionFactory *- kt* - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 这是自定义 `Member` 和 `Class` 相关功能的查找匹配以及 `invoke` 的封装类。 - -### MembersType *- class* - -```kotlin -enum class MembersType -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 定义一个 `Class` 中的 `Member` 类型 - -#### ALL *- enum* - -```kotlin -ALL -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 全部 `Method` 与 `Constructor`。 - -#### METHOD *- enum* - -```kotlin -METHOD -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 全部 `Method`。 - -#### CONSTRUCTOR *- enum* - -```kotlin -CONSTRUCTOR -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 全部 `Constructor`。 - -### ClassLoader.onLoadClass *- ext-method* - -```kotlin -fun ClassLoader.onLoadClass(result: (Class<*>) -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 监听当前 `ClassLoader` 的 `ClassLoader.loadClass` 方法装载。 - -!> 请注意只有当前 `ClassLoader` 有主动使用 `ClassLoader.loadClass` 事件时才能被捕获。 - -!> 这是一个实验性功能,一般情况下不会用到此方法,不保证不会发生错误。 - -!> 只能在 (Xposed) 宿主环境使用此功能,其它环境下使用将不生效且会打印警告信息。 - -**功能示例** - -针对一些使用特定 `ClassLoader` 装载 `Class` 的宿主应用,你可以使用此方法来监听 `Class` 加载情况。 - -!> 为了防止发生问题,你需要**得到一个存在的 `ClassLoader` 实例**来使用此功能。 - -比如我们在 `PackageParam` 中使用 `appClassLoader`。 - -> 示例如下 - -```kotlin -appClassLoader.onLoadClass { clazz -> - // 得到 clazz 即加载对象 - clazz... // 这里进行你需要的操作 -} -``` - -或使用你得到的存在的 `ClassLoader` 实例,可以通过 Hook 获取。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -customClassLoader?.onLoadClass { clazz -> - // ... -} -``` - -在判断到这个 `Class` 被装载成功时,开始执行你的 Hook 功能。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -customClassLoader?.onLoadClass { clazz -> - if(clazz.name == /** 你需要的 Class 名称 */) { - clazz.hook { - // ... - } - } -} -``` - -### ~~hookClass *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `移除` - -`HookClass` 相关功能不再对外开放 - -### ~~normalClass *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `移除` - -`HookClass` 相关功能不再对外开放 - -### ~~hasClass *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `移除` - -请直接使用 `hasClass()` 无参方法 - -### Class.hasExtends *- ext-field* - -```kotlin -val Class<*>.hasExtends: Boolean -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 当前 `Class` 是否有继承关系,父类是 `Any` 将被认为没有继承关系。 - -### ~~classOf *- method*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `移除` - -请转到 `toClass(...)` 方法 - -### String.toClass *- ext-method* - -```kotlin -fun String.toClass(loader: ClassLoader?): Class<*> -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 通过字符串类名转换为 `loader` 中的实体类。 - -**功能示例** - -你可以直接填写你要查找的目标 `Class`,必须在默认 `ClassLoader` 下存在。 - -> 示例如下 - -```kotlin -"com.example.demo.DemoClass".toClass() -``` - -你还可以自定义 `Class` 所在的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -"com.example.demo.DemoClass".toClass(customClassLoader) -``` - -### classOf *- method* - -```kotlin -inline fun classOf(loader: ClassLoader?): Class<*> -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 通过 `T` 得到其 `Class` 实例并转换为实体类。 - -**功能示例** - -我们要获取一个 `Class` 在 `Kotlin` 下不通过反射时应该这样做。 - -> 示例如下 -> -```kotlin -DemoClass::class.java -``` - -现在,你可以直接 `cast` 一个实例并获取它的 `Class` 对象,必须在当前 `ClassLoader` 下存在。 - -> 示例如下 - -```kotlin -classOf() -``` - -若目标存在的 `Class` 为 `stub`,通过这种方式,你还可以自定义 `Class` 所在的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -classOf(customClassLoader) -``` - -### String.hasClass *- ext-method* - -```kotlin -fun String.hasClass(loader: ClassLoader?): Boolean -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -支持直接使用空参数方法使用默认 `ClassLoader` 进行判断 - -**功能描述** - -> 通过字符串类名使用指定的 `ClassLoader` 查找是否存在。 - -**功能示例** - -你可以轻松的使用此方法判断字符串中的类是否存在,效果等同于直接使用 `Class.forName`。 - -> 示例如下 - -```kotlin -if("com.example.demo.DemoClass".hasClass()) { - // Your code here. -} -``` - -填入方法中的 `loader` 参数可判断指定的 `ClassLoader` 中的 `Class` 是否存在。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -if("com.example.demo.DemoClass".hasClass(customClassloader)) { - // Your code here. -} -``` - -### Class.hasField *- ext-method* - -```kotlin -inline fun Class<*>.hasField(initiate: FieldCondition): Boolean -``` - -**变更记录** - -`v1.0.4` `新增` - -`v1.0.67` `修改` - -合并到 `FieldFinder` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找变量是否存在。 - -### Class.hasMethod *- ext-method* - -```kotlin -inline fun Class<*>.hasMethod(initiate: MethodConditions): Boolean -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -新增 `returnType` 参数 - -`v1.0.67` `修改` - -合并到 `MethodFinder` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找方法是否存在。 - -### Class.hasConstructor *- ext-method* - -```kotlin -inline fun Class<*>.hasConstructor(initiate: ConstructorConditions): Boolean -``` - -**变更记录** - -`v1.0.2` `新增` - -`v1.0.67` `修改` - -合并到 `ConstructorFinder` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找构造方法是否存在。 - -### Member.hasModifiers *- ext-method* - -```kotlin -inline fun Member.hasModifiers(conditions: ModifierConditions): Boolean -``` - -**变更记录** - -`v1.0.67` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `修改` - -合并到 `ModifierConditions` - -**功能描述** - -> 查询 `Member` 中匹配的描述符。 - -### Class.hasModifiers *- ext-method* - -```kotlin -inline fun Class<*>.hasModifiers(conditions: ModifierConditions): Boolean -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 查询 `Class` 中匹配的描述符。 - -### ~~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` `移除` - -### Class.field *- ext-method* - -```kotlin -inline fun Class<*>.field(initiate: FieldConditions): FieldFinder.Result -``` - -**变更记录** - -`v1.0.2` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找并得到变量。 - -### Class.method *- ext-method* - -```kotlin -inline fun Class<*>.method(initiate: MethodConditions): MethodFinder.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -~~`obtainMethod`~~ 更名为 `method` - -新增 `returnType` 参数 - -`v1.0.2` `修改` - -合并到 `MethodFinder` 方法体 - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找并得到方法。 - -### Class.constructor *- ext-method* - -```kotlin -inline fun Class<*>.constructor(initiate: ConstructorConditions): ConstructorFinder.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -~~`obtainConstructor`~~ 更名为 `constructor` - -`v1.0.2` `修改` - -合并到 `ConstructorFinder` 方法体 - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找并得到构造方法。 - -### ~~callStatic *- method*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -~~`invokeStatic`~~ 更名为 `callStatic` - -`v1.0.2` `移除` - -### ~~call *- method*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.1` `修改` - -~~`invokeAny`~~ 更名为 `call` - -`v1.0.2` `移除` - -### Any.current *- ext-method* - -```kotlin -inline fun T.current(ignored: Boolean): CurrentClass -``` - -```kotlin -inline fun T.current(ignored: Boolean, initiate: CurrentClass.() -> Unit): T -``` - -**变更记录** - -`v1.0.70` `新增` - -`v1.1.0` `新增` - -新增 `ignored` 参数,可以忽略在 `CurrentClass` 中出现的异常 - -新增不使用 `current { ... }` 调用域直接使用 `current()` 得到实例的类操作对象 - -**功能描述** - -> 获得当前实例的类操作对象。 - -### ~~Class.buildOfAny *- ext-method*~~ - -**变更记录** - -`v1.0.70` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `作废` - -请转移到 `buildOf` 方法 - -### Class.buildOf *- ext-method* - -```kotlin -inline fun Class<*>.buildOf(vararg param: Any?, initiate: ConstructorConditions): Any? -``` - -```kotlin -inline fun Class<*>.buildOf(vararg param: Any?, initiate: ConstructorConditions): T? -``` - -**变更记录** - -`v1.0.70` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -`v1.1.0` `修改` - -加入无泛型方法 `buildOf` - -**功能描述** - -> 通过构造方法创建新实例,指定类型 `T` 或任意类型 `Any`。 - -### Class.allMethods *- ext-method* - -```kotlin -inline fun Class<*>.allMethods(result: (index: Int, method: Method) -> Unit) -``` - -**变更记录** - -`v1.0.70` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 遍历当前类中的所有方法。 - -### Class.allConstructors *- ext-method* - -```kotlin -inline fun Class<*>.allConstructors(result: (index: Int, constructor: Constructor<*>) -> Unit) -``` - -**变更记录** - -`v1.0.70` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 遍历当前类中的所有构造方法。 - -### Class.allFields *- ext-method* - -```kotlin -inline fun Class<*>.allFields(result: (index: Int, field: Field) -> Unit) -``` - -**变更记录** - -`v1.0.70` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 遍历当前类中的所有变量。 \ No newline at end of file diff --git a/docs/api/public/VariableTypeFactory.md b/docs/api/public/VariableTypeFactory.md deleted file mode 100644 index 431eba12..00000000 --- a/docs/api/public/VariableTypeFactory.md +++ /dev/null @@ -1,11 +0,0 @@ -## 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 deleted file mode 100644 index 8850a8e5..00000000 --- a/docs/api/public/VariousClass.md +++ /dev/null @@ -1,29 +0,0 @@ -## 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 deleted file mode 100644 index 861e70e4..00000000 --- a/docs/api/public/ViewTypeFactory.md +++ /dev/null @@ -1,11 +0,0 @@ -## 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 deleted file mode 100644 index d34ce145..00000000 --- a/docs/api/public/YukiBaseHooker.md +++ /dev/null @@ -1,27 +0,0 @@ -## 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 deleted file mode 100644 index 548831e5..00000000 --- a/docs/api/public/YukiHookAPI.md +++ /dev/null @@ -1,513 +0,0 @@ -## 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*~~ - -**变更记录** - -`v1.0.5` `新增` - -`v1.0.91` `移除` - -请转移到 `Status.executorName` - -### ~~executorVersion *- field*~~ - -**变更记录** - -`v1.0.5` `新增` - -`v1.0.91` `移除` - -请转移到 `Status.executorVersion` - -### Status *- object* - -```kotlin -object Status -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 当前 `YukiHookAPI` 的状态。 - -#### isXposedEnvironment *- field* - -```kotlin -val isXposedEnvironment: Boolean -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 获取当前是否为 (Xposed) 宿主环境。 - -#### executorName *- field* - -```kotlin -val executorName: String -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 获取当前 Hook 框架的名称。 - -无法获取会返回 `unknown`,`XposedBridge` 不存在会返回 `invalid`。 - -!> 在模块环境中需要启用 `Configs.isEnableHookModuleStatus`。 - -#### executorVersion *- field* - -```kotlin -val executorVersion: Int -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 获取当前 Hook 框架的版本。 - -无法获取会返回 `-1`。 - -!> 在模块环境中需要启用 `Configs.isEnableHookModuleStatus`。 - -#### isModuleActive *- field* - -```kotlin -val isModuleActive: Boolean -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 判断模块是否在 Xposed 或太极、无极中激活。 - -!> 在模块环境中你需要将 `Application` 继承于 `ModuleApplication`。 - -!> 在模块环境中需要启用 `Configs.isEnableHookModuleStatus`。 - -!> 在 (Xposed) 宿主环境中仅返回非 `isTaiChiModuleActive` 的激活状态。 - -#### isXposedModuleActive *- field* - -```kotlin -val isXposedModuleActive: Boolean -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 仅判断模块是否在 Xposed 中激活。 - -!> 在模块环境中需要启用 `Configs.isEnableHookModuleStatus`。 - -!> 在 (Xposed) 宿主环境中始终返回 true。 - -#### isTaiChiModuleActive *- field* - -```kotlin -val isTaiChiModuleActive: Boolean -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 仅判断模块是否在太极、无极中激活。 - -!> 在模块环境中你需要将 `Application` 继承于 `ModuleApplication`。 - -!> 在 (Xposed) 宿主环境中始终返回 false。 - -#### isSupportResourcesHook *- field* - -```kotlin -val isSupportResourcesHook: Boolean -``` - -**变更记录** - -`v1.0.91` `新增` - -**功能描述** - -> 判断当前 Hook Framework 是否支持资源钩子(Resources Hook)。 - -!> 在模块环境中需要启用 `Configs.isEnableHookModuleStatus`。 - -!> 在 (Xposed) 宿主环境中可能会延迟等待事件回调后才会返回 true。 - -!> 请注意你需要确保 `InjectYukiHookWithXposed.isUsingResourcesHook` 已启用,否则始终返回 false。 - -### 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` 中自由开启和关闭缓存功能以及清除缓存。 - -#### isEnableModuleAppResourcesCache *- field* - -```kotlin -var isEnableModuleAppResourcesCache: Boolean -``` - -**变更记录** - -`v1.0.87` `新增` - -**功能描述** - -> 是否启用当前 Xposed 模块自身 `Resources` 缓存功能。 - -为防止内存复用过高问题,此功能默认启用。 - -你可以手动调用 `PackageParam.refreshModuleAppResources` 来刷新缓存。 - -!> 关闭后每次使用 `PackageParam.moduleAppResources` 都会重新创建,可能会造成运行缓慢。 - -#### isEnableHookModuleStatus *- field* - -```kotlin -var isEnableHookModuleStatus: Boolean -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 是否启用 Hook Xposed 模块激活等状态功能. - -为原生支持 Xposed 模块激活状态检测,此功能默认启用。 - -!> 关闭后你将不能再在模块环境中使用 `YukiHookAPI.Status` 中的功能。 - -#### isEnableHookSharedPreferences *- field* - -```kotlin -var isEnableHookSharedPreferences: Boolean -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 是否启用 Hook `SharedPreferences`。 - -启用后将在模块启动时强制将 `SharedPreferences` 文件权限调整为 `Context.MODE_WORLD_READABLE` (0664)。 - -!> 这是一个可选的实验性功能,此功能默认不启用。 - -仅用于修复某些系统可能会出现在启用了 `New XSharedPreferences` 后依然出现文件权限错误问题,若你能正常使用 `YukiHookModulePrefs` 就不建议启用此功能。 - -#### isEnableDataChannel *- field* - -```kotlin -var isEnableDataChannel: Boolean -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 是否启用当前 Xposed 模块与宿主交互的 `YukiHookDataChannel` 功能。 - -请确保 Xposed 模块的 `Application` 继承于 `ModuleApplication` 才能有效。 - -此功能默认启用,关闭后将不会在功能初始化的时候装载 `YukiHookDataChannel`。 - -#### isEnableMemberCache *- field* - -```kotlin -var isEnableMemberCache: Boolean -``` - -**变更记录** - -`v1.0.68` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 是否启用 `Member` 缓存功能。 - -为防止 `Member` 复用过高造成的系统 GC 问题,此功能默认启用。 - -启用后会缓存已经找到的 `Method`、`Constructor`、`Field`。 - -缓存的 `Member` 都将处于 `MemberCacheStore` 的全局静态实例中。 - -推荐使用 `MethodFinder`、`ConstructorFinder`、`FieldFinder` 来获取 `Member`。 - -除非缓存的 `Member` 发生了混淆的问题,例如使用 R8 混淆后的 APP 的目标 `Member`,否则建议启用。 - -### configs *- method* - -```kotlin -inline fun configs(initiate: Configs.() -> Unit) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 对 `Configs` 类实现了一个 `lambda` 方法体。 - -你可以轻松的调用它进行配置。 - -**功能示例** - -你可以在 `HookEntryClass` 的 `onInit` 方法中调用 `configs` 方法完成对 API 的功能配置,实时生效。 - -> 示例如下 - -```kotlin -class HookEntryClass : IYukiHookXposedInit { - - override fun onInit() { - YukiHookAPI.configs { - debugTag = "YukiHookAPI" - isDebug = BuildConfig.DEBUG - isAllowPrintingLogs = true - isEnableModulePrefsCache = true - isEnableModuleAppResourcesCache = true - isEnableHookModuleStatus = true - isEnableDataChannel = true - isEnableMemberCache = true - } - } - - override fun onHook() { - // Your code here. - } -} -``` - -若觉得上面的写法不美观,你还可以写得更加简洁。 - -> 示例如下 - -```kotlin -class HookEntryClass : IYukiHookXposedInit { - - override fun onInit() = configs { - debugTag = "YukiHookAPI" - isDebug = BuildConfig.DEBUG - isAllowPrintingLogs = true - isEnableModulePrefsCache = true - isEnableModuleAppResourcesCache = true - isEnableHookModuleStatus = true - isEnableDataChannel = true - isEnableMemberCache = true - } - - override fun onHook() { - // Your code here. - } -} -``` - -你也可以不通过 `configs` 方法,直接进行配置。 - -> 示例如下 - -```kotlin -class HookEntryClass : IYukiHookXposedInit { - - override fun onInit() { - YukiHookAPI.Configs.debugTag = "YukiHookAPI" - YukiHookAPI.Configs.isDebug = BuildConfig.DEBUG - YukiHookAPI.Configs.isAllowPrintingLogs = true - YukiHookAPI.Configs.isEnableModulePrefsCache = true - YukiHookAPI.Configs.isEnableModuleAppResourcesCache = true - YukiHookAPI.Configs.isEnableHookModuleStatus = true - YukiHookAPI.Configs.isEnableDataChannel = 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/YukiHookDataChannel.md b/docs/api/public/YukiHookDataChannel.md deleted file mode 100644 index a859afdd..00000000 --- a/docs/api/public/YukiHookDataChannel.md +++ /dev/null @@ -1,141 +0,0 @@ -## YukiHookDataChannel *- class* - -```kotlin -class YukiHookDataChannel private constructor() -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 实现 Xposed 模块的数据通讯桥。 - -通过模块与宿主相互注册 `BroadcastReceiver` 来实现数据的交互。 - -模块需要将 `Application` 继承于 `ModuleApplication` 来实现此功能。 - -!> 模块与宿主需要保持存活状态,否则无法建立通讯。 - -### NameSpace *- class* - -```kotlin -inner class NameSpace internal constructor(private val context: Context?, private val packageName: String, private val isSecure: Boolean) -``` - -**变更记录** - -`v1.0.88` `新增` - -`v1.0.90` `修改` - -新增 `isSecure` 参数 - -**功能描述** - -> `YukiHookDataChannel` 命名空间。 - -#### with *- method* - -```kotlin -inline fun with(initiate: NameSpace.() -> Unit): NameSpace -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 创建一个调用空间。 - -#### put *- method* - -```kotlin -fun put(key: String, value: T) -``` - -```kotlin -fun put(data: ChannelData, value: T?) -``` - -```kotlin -fun put(vararg data: ChannelData<*>) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 发送键值数据。 - -#### put *- method* - -```kotlin -fun put(key: String) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 仅发送键值监听,使用默认值 `VALUE_WAIT_FOR_LISTENER` 发送键值数据。 - -#### wait *- method* - -```kotlin -fun wait(key: String, result: (value: T) -> Unit) -``` - -```kotlin -fun wait(data: ChannelData, result: (value: T) -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -`v1.0.90` `修改` - -移除默认值 `value` - -**功能描述** - -> 获取键值数据。 - -#### wait *- method* - -```kotlin -fun wait(key: String, callback: () -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 仅获取监听结果,不获取键值数据。 - -!> 仅限使用 `VALUE_WAIT_FOR_LISTENER` 发送的监听才能被接收。 - -#### checkingVersionEquals *- method* - -```kotlin -fun checkingVersionEquals(result: (Boolean) -> Unit) -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 获取模块与宿主的版本是否匹配。 - -通过此方法可原生判断 Xposed 模块更新后宿主并未重新装载造成两者不匹配的情况。 \ No newline at end of file diff --git a/docs/api/public/YukiHookFactory.md b/docs/api/public/YukiHookFactory.md deleted file mode 100644 index fc6540ae..00000000 --- a/docs/api/public/YukiHookFactory.md +++ /dev/null @@ -1,225 +0,0 @@ -## YukiHookFactory *- kt* - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -合并到 `IYukiHookXposedInit`,将方法体进行 inline - -**功能描述** - -> 这是 `YukiHookAPI` 相关 `lambda` 方法的封装类以及部分 API 用法。 - -### IYukiHookXposedInit.configs *- ext-method* - -```kotlin -inline fun IYukiHookXposedInit.configs(initiate: YukiHookAPI.Configs.() -> Unit) -``` - -**变更记录** - -`v1.0.1` `新增` - -`v1.0.80` `修改` - -合并到 `IYukiHookXposedInit` - -**功能描述** - -> 在 `IYukiHookXposedInit` 中配置 `Configs`。 - -### IYukiHookXposedInit.encase *- ext-method* - -```kotlin -fun IYukiHookXposedInit.encase(initiate: PackageParam.() -> Unit) -``` - -```kotlin -fun IYukiHookXposedInit.encase(vararg hooker: YukiBaseHooker) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -合并到 `IYukiHookXposedInit` - -**功能描述** - -> 在 `IYukiHookXposedInit` 中调用 `YukiHookAPI`。 - -### Context.modulePrefs *- ext-field* - -```kotlin -val Context.modulePrefs: YukiHookModulePrefs -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获取模块的存取对象。 - -### Context.modulePrefs *- ext-method* - -```kotlin -fun Context.modulePrefs(name: String): YukiHookModulePrefs -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获取模块的存取对象,可设置 `name` 为自定义 Sp 存储名称。 - -### Context.dataChannel *- ext-method* - -```kotlin -fun Context.dataChannel(packageName: String): YukiHookDataChannel.NameSpace -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 获取模块的数据通讯桥命名空间对象。 - -!> 只能在模块环境使用此功能,其它环境下使用将不起作用。 - -### Context.processName *- ext-field* - -```kotlin -val Context.processName: String -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 获取当前进程名称。 - -### Context+Resources.injectModuleAppResources *- ext-method* - -```kotlin -fun Context.injectModuleAppResources() -``` - -```kotlin -fun Resources.injectModuleAppResources() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 向 Hook APP (宿主) `Context` 或 `Resources` 注入当前 Xposed 模块的资源。 - -注入成功后,你就可以直接使用例如 `ImageView.setImageResource` 或 `Resources.getString` 装载当前 Xposed 模块的资源 ID。 - -注入的资源作用域仅限当前 `Context` 或 `Resources`,你需要在每个用到宿主 `Context` 或 `Resources` 的地方重复调用此方法进行注入才能使用。 - -!> 只能在 (Xposed) 宿主环境使用此功能,其它环境下使用将不生效且会打印警告信息。 - -### Context.registerModuleAppActivities *- ext-method* - -```kotlin -fun Context.registerModuleAppActivities(proxy: Any?) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 向 Hook APP (宿主) 注册当前 Xposed 模块的 `Activity`。 - -注册成功后,你就可以直接使用 `Context.startActivity` 来启动未在宿主中注册的 `Activity`。 - -使用此方法会在未注册的 `Activity` 在 Hook APP (宿主) 中启动时自动调用 `injectModuleAppResources` 注入当前 Xposed 模块的资源。 - -你要将需要在宿主启动的 `Activity` 继承于 `ModuleAppActivity` 或 `ModuleAppCompatActivity`。 - -!> 只能在 (Xposed) 宿主环境使用此功能,其它环境下使用将不生效且会打印警告信息。 - -### Context.applyModuleTheme *- ext-method* - -```kotlin -fun Context.applyModuleTheme(theme: Int, configuration: Configuration?): ModuleContextThemeWrapper -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 生成一个 `ContextThemeWrapper` 代理以应用当前 Xposed 模块的主题资源。 - -在 Hook APP (宿主) 中使用此方法会自动调用 `injectModuleAppResources` 注入当前 Xposed 模块的资源。 - -如果在 Hook APP (宿主) 中使用此方法发生 `ClassCastException`,请手动设置 `configuration`。 - -### ~~isSupportResourcesHook *- field*~~ - -**变更记录** - -`v1.0.80` `新增` - -`v1.0.91` `移除` - -请转移到 `YukiHookAPI.Status.isSupportResourcesHook` - -### ~~isModuleActive *- field*~~ - -**变更记录** - -`v1.0.6` `新增` - -`v1.0.91` `移除` - -请转移到 `YukiHookAPI.Status.isModuleActive` - -### ~~isXposedModuleActive *- field*~~ - -**变更记录** - -`v1.0.6` `新增` - -`v1.0.91` `移除` - -请转移到 `YukiHookAPI.Status.isXposedModuleActive` - -### ~~isTaiChiModuleActive *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.91` `移除` - -请转移到 `YukiHookAPI.Status.isTaiChiModuleActive` - -## ~~YukiHookModuleStatus *- class*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.0.91` `作废` - -请转移到 `YukiHookAPI.Status` \ No newline at end of file diff --git a/docs/api/public/YukiHookModulePrefs.md b/docs/api/public/YukiHookModulePrefs.md deleted file mode 100644 index d91bdbc4..00000000 --- a/docs/api/public/YukiHookModulePrefs.md +++ /dev/null @@ -1,427 +0,0 @@ -## YukiHookModulePrefs *- class* - -```kotlin -class YukiHookModulePrefs private constructor(private var 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)。 - -若你在按照规定配置后依然无法使用或出现文件权限错误问题,可以参考 [isEnableHookSharedPreferences](api/document?id=isenablehooksharedpreferences-field)。 - -未使用 LSPosed 环境请将你的模块 `API` 降至 `26` 以下,`YukiHookAPI` 将会尝试使用 `makeWorldReadable` 但仍有可能不成功。 - -太极请参阅 [文件权限/配置/XSharedPreference](https://taichi.cool/zh/doc/for-xposed-dev.html#文件权限-配置-xsharedpreference)。 - -!> 当你在 Xposed 模块中存取数据的时候 `context` 必须不能是空的。 - -若你正在使用 `PreferenceFragmentCompat`,请迁移到 `ModulePreferenceFragment` 以适配上述功能特性。 - -**可选配置** - -若你不想将你的模块的 `xposedminversion` 最低设置为 `93`,你可以在 `AndroidManifest.xml` 中添加 `xposedsharedprefs` 来实现支持。 - -详见 [New XSharedPreferences](https://github.com/LSPosed/LSPosed/wiki/New-XSharedPreferences#for-the-module)。 - -> 示例如下 - -```xml - -``` - -### isXSharePrefsReadable *- field* - -```kotlin -val isXSharePrefsReadable: Boolean -``` - -**变更记录** - -`v1.0.90` `新增` - -**功能描述** - -> 获取 `XSharedPreferences` 是否可读。 - -!> 只能在 (Xposed) 宿主环境中使用,模块环境中始终返回 false。 - -### isRunInNewXShareMode *- field* - -```kotlin -val isRunInNewXShareMode: Boolean -``` - -**变更记录** - -`v1.0.78` `新增` - -**功能描述** - -> 获取 `YukiHookModulePrefs` 是否正处于 EdXposed/LSPosed 的最高权限运行。 - -前提条件为当前 Xposed 模块已被激活。 - -!> 只能在模块环境中使用,(Xposed) 宿主环境中始终返回 false - -### 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` 键值。 - -### getStringSet *- method* - -```kotlin -fun getStringSet(key: String, value: Set): Set -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 获取 `Set` 键值。 - -### 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` 键值。 - -### all *- method* - -```kotlin -fun all(): HashMap -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 获取全部存储的键值数据。 - -智能识别对应环境读取键值数据。 - -!> 每次调用都会获取实时的数据,不受缓存控制,请勿在高并发场景中使用。 - -### remove *- method* - -```kotlin -fun remove(key: String) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 移除全部包含 `key` 的存储数据。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### remove *- method* - -```kotlin -inline fun remove(prefs: PrefsData) -``` - -**变更记录** - -`v1.0.67` `新增` - -**功能描述** - -> 移除 `PrefsData.key` 的存储数据。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### clear *- method* - -```kotlin -fun clear() -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 移除全部存储数据。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### putString *- method* - -```kotlin -fun putString(key: String, value: String) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 存储 `String` 键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### putStringSet *- method* - -```kotlin -fun putStringSet(key: String, value: Set) -``` - -**变更记录** - -`v1.0.77` `新增` - -**功能描述** - -> 存储 `Set` 键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### putBoolean *- method* - -```kotlin -fun putBoolean(key: String, value: Boolean) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 存储 `Boolean` 键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### putInt *- method* - -```kotlin -fun putInt(key: String, value: Int) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 存储 `Int` 键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### putLong *- method* - -```kotlin -fun putLong(key: String, value: Long) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 存储 `Long` 键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### putFloat *- method* - -```kotlin -fun putFloat(key: String, value: Float) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 存储 `Float` 键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### 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` `新增` - -**功能描述** - -> 智能存储指定类型的键值。 - -!> 在 (Xposed) 宿主环境下只读,无法使用。 - -### clearCache *- method* - -```kotlin -fun clearCache() -``` - -**变更记录** - -`v1.0.5` `新增` - -**功能描述** - -> 清除 `XSharedPreferences` 中缓存的键值数据。 - -无论是否开启 `YukiHookAPI.Configs.isEnableModulePrefsCache`。 - -调用此方法将清除当前存储的全部键值缓存。 - -下次将从 `XSharedPreferences` 重新读取。 - -在 (Xposed) 宿主环境中使用。 \ No newline at end of file diff --git a/docs/api/public/YukiMemberHookCreator.md b/docs/api/public/YukiMemberHookCreator.md deleted file mode 100644 index 3b1a2d9f..00000000 --- a/docs/api/public/YukiMemberHookCreator.md +++ /dev/null @@ -1,973 +0,0 @@ -## YukiMemberHookCreator *- class* - -```kotlin -class YukiMemberHookCreator(internal val packageParam: PackageParam, internal val hookClass: HookClass) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -对 `hookClass` 进行 inline 处理 - -`v1.1.0` `修改` - -修正拼写错误的 **Creater** 命名到 **Creator** - -**功能描述** - -> `YukiHookAPI` 的 `Member` 核心 Hook 实现类。 - -### PRIORITY_DEFAULT *- field* - -```kotlin -val PRIORITY_DEFAULT: Int -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 默认 Hook 回调优先级。 - -### PRIORITY_LOWEST *- field* - -```kotlin -val PRIORITY_LOWEST: Int -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 延迟回调 Hook 方法结果。 - -### PRIORITY_HIGHEST *- field* - -```kotlin -val PRIORITY_HIGHEST: Int -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 更快回调 Hook 方法结果。 - -### instanceClass *- field* - -```kotlin -val instanceClass: Class<*> -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.2` `修改` - -~~`thisClass`~~ 更名为 `instanceClass` - -**功能描述** - -> 得到当前被 Hook 的 `Class`。 - -!> 不推荐直接使用,万一得不到 `Class` 对象则会无法处理异常导致崩溃。 - -### injectMember *- method* - -```kotlin -inline fun injectMember(priority: Int, tag: String, initiate: MemberHookCreator.() -> Unit): MemberHookCreator.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -增加 `priority` Hook 优先级 - -**功能描述** - -> 注入要 Hook 的 `Method`、`Constructor`。 - -**功能示例** - -你可以注入任意 `Method` 与 `Constructor`,使用 `injectMember` 即可创建一个 `Hook` 对象。 - -> 示例如下 - -```kotlin -injectMember { - // Your code here. -} -``` - -你还可以自定义 `tag`,方便你在调试的时候能够区分你的 `Hook` 对象。 - -> 示例如下 - -```kotlin -injectMember(tag = "KuriharaYuki") { - // Your code here. -} -``` - -你还可以自定义 `priority`,以控制当前 Hook 对象并列执行的优先级速度。 - -> 示例如下 - -```kotlin -injectMember(priority = PRIORITY_HIGHEST) { - // Your code here. -} -``` - -### useDangerousOperation *- method* - -```kotlin -fun useDangerousOperation(option: String) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 允许 Hook 过程中的所有危险行为。 - -请在 `option` 中键入 `Yes do as I say!` 代表你同意允许所有危险行为。 - -你还需要在整个作用域中声明注解 `CauseProblemsApi` 以消除警告。 - -若你只需要 Hook `ClassLoader` 的 `loadClass` 方法,请参考 [ClassLoader.onLoadClass](api/document?id=classloaderonloadclass-ext-method)。 - -!> 若你不知道允许此功能会带来何种后果,请勿使用。 - -### MemberHookCreator *- class* - -```kotlin -inner class MemberHookCreator internal constructor(private val priority: Int, internal val tag: String) -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -增加 `priority` Hook 优先级 - -`v1.0.81` `修改` - -增加 `packageName` 当前 Hook 的 APP 包名 - -`v1.1.0` `修改` - -移除 `packageName` - -修正拼写错误的 **Creater** 命名到 **Creator** - -**功能描述** - -> Hook 核心功能实现类,查找和处理需要 Hook 的 `Method`、`Constructor`。 - -#### ~~member *- field*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `移除` - -请转移到 `members` - -#### members *- method* - -```kotlin -fun members(vararg member: Member?) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 手动指定要 Hook 的 `Method`、`Constructor`。 - -!> 不建议使用此方法设置目标需要 Hook 的 `Member` 对象,你可以使用 `method` 或 `constructor` 方法。 - -**功能示例** - -你可以调用 `instanceClass` 来手动查询要 Hook 的 `Method`、`Constructor`。 - -> 示例如下 - -```kotlin -injectMember { - members(instanceClass.getDeclaredMethod("test", StringType)) - beforeHook {} - afterHook {} -} -``` - -同样地,你也可以传入一组 `Member` 同时进行 Hook。 - -> 示例如下 - -```kotlin -injectMember { - members( - instanceClass.getDeclaredMethod("test1", StringType), - instanceClass.getDeclaredMethod("test2", StringType), - instanceClass.getDeclaredMethod("test3", StringType) - ) - beforeHook {} - afterHook {} -} -``` - -#### ~~allMethods *- method*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `作废` - -请使用 `method { name = /** name */ }.all()` 来取代它 - -#### ~~allConstructors *- method*~~ - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `作废` - -请使用 `allMembers(MembersType.CONSTRUCTOR)` 来取代它 - -#### allMembers *- method* - -```kotlin -fun allMembers(type: MembersType) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 查找并 Hook `hookClass` 中的全部 `Method`、`Constructor`。 - -!> 警告:无法准确处理每个 `Member` 的返回值和 `param`,建议使用 `method` or `constructor` 对每个 `Member` 单独 Hook。 - -#### method *- method* - -```kotlin -inline fun method(initiate: MethodConditions): MethodFinder.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找当前 `Class` 需要 Hook 的 `Method` 。 - -**功能示例** - -你可参考 [MethodFinder](#methodfinder-class) 查看详细用法。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "test" - param(StringType) - returnType = UnitType - } - beforeHook {} - afterHook {} -} -``` - -若想 Hook 当前查询 `method { ... }` 条件的全部结果,你只需要在最后加入 `all` 即可。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "test" - paramCount(1..5) - }.all() - beforeHook {} - afterHook {} -} -``` - -此时 `beforeHook` 与 `afterHook` 会在每个查询到的结果中多次回调 Hook 方法体。 - -!> 若没有 `all`,默认只会 Hook 当前条件查询到的数组下标结果第一位。 - -#### constructor *- method* - -```kotlin -inline fun constructor(initiate: ConstructorConditions): ConstructorFinder.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 查找当前 `Class` 需要 Hook 的 `Constructor`。 - -**功能示例** - -你可参考 [ConstructorFinder](#constructorfinder-class) 查看详细用法。 - -> 示例如下 - -```kotlin -injectMember { - constructor { param(StringType) } - beforeHook {} - afterHook {} -} -``` - -若想 Hook 当前查询 `constructor { ... }` 条件的全部结果,你只需要在最后加入 `all` 即可。 - -> 示例如下 - -```kotlin -injectMember { - constructor { paramCount(1..5) }.all() - beforeHook {} - afterHook {} -} -``` - -此时 `beforeHook` 与 `afterHook` 会在每个查询到的结果中多次回调 Hook 方法体。 - -!> 若没有 `all`,默认只会 Hook 当前条件查询到的数组下标结果第一位。 - -#### HookParam.field *- i-ext-method* - -```kotlin -inline fun HookParam.field(initiate: FieldConditions): FieldFinder.Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 使用当前 `hookClass` 查找并得到 `Field`。 - -**功能示例** - -你可参考 [FieldFinder](#fieldfinder-class) 查看详细用法。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "test" - param(StringType) - returnType = UnitType - } - afterHook { - // 这里不需要再调用 instanceClass.field 进行查询 - field { - name = "isSweet" - type = BooleanType - }.get(instance).setTrue() - } -} -``` - -#### HookParam.method *- i-ext-method* - -```kotlin -inline fun HookParam.method(initiate: MethodConditions): MethodFinder.Result -``` - -**变更记录** - -`v1.0.2` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 使用当前 `hookClass` 查找并得到 `Method` 。 - -#### HookParam.constructor *- i-ext-method* - -```kotlin -inline fun HookParam.constructor(initiate: ConstructorConditions): ConstructorFinder.Result -``` - -**变更记录** - -`v1.0.2` `添加` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 使用当前 `hookClass` 查找并得到 `Constructor`。 - -#### HookParam.injectMember *- i-ext-method* - -```kotlin -inline fun HookParam.injectMember(priority: Int, tag: String, initiate: MemberHookCreator.() -> Unit): MemberHookCreator.Result -``` - -**变更记录** - -`v1.0.88` `新增` - -**功能描述** - -> 注入要 Hook 的 `Method`、`Constructor` (嵌套 Hook)。 - -#### beforeHook *- method* - -```kotlin -fun beforeHook(initiate: HookParam.() -> Unit): HookCallback -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `HookCallback` 返回类型 - -**功能描述** - -> 在 `Member` 执行完成前 Hook。 - -#### afterHook *- method* - -```kotlin -fun afterHook(initiate: HookParam.() -> Unit): HookCallback -``` - -**变更记录** - -`v1.0` `添加` - -`v1.1.0` `修改` - -新增 `HookCallback` 返回类型 - -**功能描述** - -> 在 `Member` 执行完成后 Hook。 - -#### replaceAny *- method* - -```kotlin -fun replaceAny(initiate: HookParam.() -> Any?) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 拦截并替换此 `Member` 内容,给出返回值。 - -#### replaceUnit *- method* - -```kotlin -fun replaceUnit(initiate: HookParam.() -> Unit) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 拦截并替换此 `Member` 内容,没有返回值,可以称为 `Void`。 - -#### replaceTo *- method* - -```kotlin -fun replaceTo(any: Any?) -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 拦截并替 `Member` 返回值。 - -#### replaceToTrue *- method* - -```kotlin -fun replaceToTrue() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 拦截并替换 `Member` 返回值为 `true`。 - -!> 确保替换 `Member` 的返回对象为 `Boolean`。 - -#### replaceToFalse *- method* - -```kotlin -fun replaceToFalse() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 拦截并替换 `Member` 返回值为 `false`。 - -!> 确保替换 `Member` 的返回对象为 `Boolean`。 - -#### intercept *- method* - -```kotlin -fun intercept() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 拦截此 `Member` 。 - -!> 这将会禁止此 `Member` 执行并返回 `null`。 - -!> 注意:例如 `Int`、`Long`、`Boolean` 常量返回值的 `Member` 一旦被设置为 null 可能会造成 Hook APP 抛出异常。 - -#### removeSelf *- method* - -```kotlin -fun removeSelf(result: (Boolean) -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 移除当前注入的 Hook `Method`、`Constructor` (解除 Hook)。 - -!> 你只能在 Hook 回调方法中使用此功能。 - -#### HookCallback *- class* - -```kotlin -inner class HookCallback internal constructor() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> Hook 方法体回调实现类。 - -##### onFailureThrowToApp *- method* - -```kotlin -fun onFailureThrowToApp() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 当回调方法体内发生异常时将异常抛出给当前 Hook APP。 - -#### Result *- class* - -```kotlin -inner class Result internal constructor() -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 监听 Hook 结果实现类。 - -##### result *- method* - -```kotlin -inline fun result(initiate: Result.() -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -`v1.0.5` `修改` - -~~`failures`~~ 修改为 `result` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建监听失败事件方法体。 - -**功能示例** - -你可以使用此方法为 `Result` 类创建 `lambda` 方法体。 - -> 示例如下 - -```kotlin -injectMember { - // Your code here. -}.result { - onHooked {} - onAlreadyHooked {} - ignoredConductFailure() - onHookingFailure {} - // ... -} -``` - -##### by *- method* - -```kotlin -inline fun by(condition: () -> Boolean): Result -``` - -**变更记录** - -`v1.0.5` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 添加执行 Hook 需要满足的条件,不满足条件将直接停止 Hook。 - -##### onHooked *- method* - -```kotlin -fun onHooked(result: (Member) -> Unit): Result -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 监听 `members` Hook 成功的回调方法。 - -在首次 Hook 成功后回调。 - -在重复 Hook 时会回调 `onAlreadyHooked`。 - -##### onAlreadyHooked *- method* - -```kotlin -fun onAlreadyHooked(result: (Member) -> Unit): Result -``` - -**变更记录** - -`v1.0.89` `新增` - -**功能描述** - -> 监听 `members` 重复 Hook 的回调方法。 - -!> 同一个 `hookClass` 中的同一个 `members` 不会被 API 重复 Hook,若由于各种原因重复 Hook 会回调此方法。 - -##### onNoSuchMemberFailure *- method* - -```kotlin -fun onNoSuchMemberFailure(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0.5` `新增` - -**功能描述** - -> 监听 `members` 不存在发生错误的回调方法。 - -##### onConductFailure *- method* - -```kotlin -fun onConductFailure(result: (HookParam, Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 监听 Hook 进行过程中发生错误的回调方法。 - -##### onHookingFailure *- method* - -```kotlin -fun onHookingFailure(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 监听 Hook 开始时发生的错误的回调方法。 - -##### onAllFailure *- method* - -```kotlin -fun onAllFailure(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0` `添加` - -**功能描述** - -> 监听全部 Hook 过程发生错误的回调方法。 - -##### ignoredNoSuchMemberFailure *- method* - -```kotlin -fun ignoredNoSuchMemberFailure(): Result -``` - -**变更记录** - -`v1.0.5` `新增` - -**功能描述** - -> 忽略 `members` 不存在发生的错误。 - -##### 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 过程发生的错误。 - -##### remove *- method* - -```kotlin -fun remove(result: (Boolean) -> Unit) -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 移除当前注入的 Hook `Method`、`Constructor` (解除 Hook)。 - -!> 你只能在 Hook 成功后才能解除 Hook,可监听 `onHooked` 事件。 - -### Result *- class* - -```kotlin -inner class Result internal constructor() -``` - -**变更记录** - -`v1.0.3` `新增` - -**功能描述** - -> 监听全部 Hook 结果实现类。 - -#### result *- method* - -```kotlin -inline fun result(initiate: Result.() -> Unit): Result -``` - -**变更记录** - -`v1.0.3` `新增` - -`v1.0.5` `修改` - -~~`failures`~~ 修改为 `result` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 创建监听事件方法体。 - -#### by *- method* - -```kotlin -inline fun by(condition: () -> Boolean): Result -``` - -**变更记录** - -`v1.0.5` `新增` - -`v1.0.80` `修改` - -将方法体进行 inline - -**功能描述** - -> 添加执行 Hook 需要满足的条件,不满足条件将直接停止 Hook。 - -#### onPrepareHook *- method* - -```kotlin -fun onPrepareHook(callback: () -> Unit): Result -``` - -**变更记录** - -`v1.0.70` `新增` - -**功能描述** - -> 监听 `hookClass` 存在时准备开始 Hook 的操作。 - -#### onHookClassNotFoundFailure *- method* - -```kotlin -fun onHookClassNotFoundFailure(result: (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/YukiModuleResources.md b/docs/api/public/YukiModuleResources.md deleted file mode 100644 index dd0fbd85..00000000 --- a/docs/api/public/YukiModuleResources.md +++ /dev/null @@ -1,29 +0,0 @@ -## YukiModuleResources *- class* - -```kotlin -class YukiModuleResources private constructor(private val baseInstance: XModuleResources) : Resources -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 对接 `XModuleResources` 的中间层实例。 - -### fwd *- method* - -```kotlin -fun fwd(resId: Int): YukiResForwarder -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 对接 `XModuleResources.fwd` 方法。 - -创建 `YukiResForwarder` 与 `XResForwarder` 实例。 \ No newline at end of file diff --git a/docs/api/public/YukiResForwarder.md b/docs/api/public/YukiResForwarder.md deleted file mode 100644 index 95206a41..00000000 --- a/docs/api/public/YukiResForwarder.md +++ /dev/null @@ -1,51 +0,0 @@ -## YukiResForwarder *- class* - -```kotlin -class YukiResForwarder private constructor(private val baseInstance: XResForwarder) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 对接 `XResForwarder` 的中间层实例。 - -### ~~instance *- field*~~ - -**变更记录** - -`v1.0.80` `新增` - -`v1.1.0` `作废` - -不再对外公开 `instance` 参数 - -### id *- field* - -```kotlin -val id: Int -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获得当前 APP 的 Resources Id。 - -### resources *- field* - -```kotlin -val resources: Resources -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获得当前 APP 的 Resources。 \ No newline at end of file diff --git a/docs/api/public/YukiResources.md b/docs/api/public/YukiResources.md deleted file mode 100644 index 42ff760e..00000000 --- a/docs/api/public/YukiResources.md +++ /dev/null @@ -1,77 +0,0 @@ -## YukiResources *- class* - -```kotlin -class YukiResources private constructor(private val baseInstance: XResources) : Resources -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 对接 `XResources` 的中间层实例。 - -### LayoutInflatedParam *- class* - -```kotlin -class LayoutInflatedParam(internal val baseParam: XC_LayoutInflated.LayoutInflatedParam) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 装载 Hook APP 的目标布局 Resources 实现类。 - -#### variantName *- field* - -```kotlin -val variantName: String -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获取当前被 Hook 的布局装载目录名称。 - -例如:`layout`、`layout-land`、`layout-sw600dp`。 - -#### currentView *- field* - -```kotlin -val currentView: View -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 获取当前被 Hook 的布局实例。 - -#### findViewByIdentifier *- method* - -```kotlin -inline fun View.findViewByIdentifier(name: String): T? -``` - -```kotlin -inline fun findViewByIdentifier(name: String): T? -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 使用 Identifier 查找 Hook APP 指定 Id 的 `View`。 - -扩展方法可以使用 Identifier 查找 Hook APP 当前装载布局中指定 Id 的 `View`。 \ No newline at end of file diff --git a/docs/api/public/YukiResourcesHookCreator.md b/docs/api/public/YukiResourcesHookCreator.md deleted file mode 100644 index 11292edb..00000000 --- a/docs/api/public/YukiResourcesHookCreator.md +++ /dev/null @@ -1,593 +0,0 @@ -## YukiResourcesHookCreator *- class* - -```kotlin -class YukiResourcesHookCreator(internal val packageParam: PackageParam, internal val hookResources: HookResources) -``` - -**变更记录** - -`v1.0.80` `新增` - -`v1.1.0` `修改` - -修正拼写错误的 **Creater** 命名到 **Creator** - -**功能描述** - -> `YukiHookAPI` 的 `Resources` 核心 Hook 实现类。 - -### injectResource *- method* - -```kotlin -inline fun injectResource(tag: String, initiate: ResourceHookCreator.() -> Unit): ResourceHookCreator.Result -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 注入要 Hook 的 Resources。 - -**功能示例** - -你可以注入任意 Resources,使用 `injectResource` 即可创建一个 `Hook` 对象。 - -> 示例如下 - -```kotlin -injectResource { - // Your code here. -} -``` - -你还可以自定义 `tag`,方便你在调试的时候能够区分你的 `Hook` 对象。 - -> 示例如下 - -```kotlin -injectResource(tag = "KuriharaYuki") { - // Your code here. -} -``` - -### ResourcesHookCreator *- class* - -```kotlin -inner class ResourcesHookCreator internal constructor(private val tag: String) -``` - -**变更记录** - -`v1.0.80` `新增` - -`v1.1.0` `修改` - -移除 `packageName` - -修正拼写错误的 **Creater** 命名到 **Creator** - -**功能描述** - -> Hook 核心功能实现类。 - -查找和处理需要 Hook 的 Resources。 - -#### resourceId *- field* - -```kotlin -var resourceId: Int -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 直接设置需要替换的 Resources Id。 - -!> 不建议使用此方法设置目标需要 Hook 的 Resources Id,你可以使用 `conditions` 方法。 - -**功能示例** - -你可以直接设置并指定目标 Hook APP 的 Resources Id。 - -> 示例如下 - -```kotlin -injectResource { - resourceId = 0x7f060001.toInt() - replaceTo(...) -} -``` - -#### conditions *- method* - -```kotlin -inline fun conditions(initiate: ConditionFinder.() -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 查找条件。 - -若你设置了 `resourceId` 则此方法将不会被使用。 - -**功能示例** - -你可参考 [ConditionFinder](#conditionfinder-class) 查看详细用法。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "test_string" - string() - } - replaceTo(...) -} -``` - -#### replaceTo *- method* - -```kotlin -fun replaceTo(any: Any) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 替换指定 Resources 为指定的值。 - -**功能示例** - -你可以替换找到的 Resources 为你想要的值,可以是 `String`、`Drawable` 等。 - -比如我们要替换一个找到的字符串 Resources。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "test_string" - string() - } - replaceTo("replace string") -} -``` - -或是替换为一个 `Drawable`,你无需对目标 Resources 实现 `fwd` 方法或 `DrawableLoader`。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "test_drawable" - drawable() - } - replaceTo(ColorDrawable(Color.RED)) -} -``` - -#### replaceToTrue *- method* - -```kotlin -fun replaceToTrue() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 替换指定 Resources 为 `true`。 - -!> 确保目标替换 Resources 的类型为 `Boolean`。 - -#### replaceToFalse *- method* - -```kotlin -fun replaceToFalse() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 替换指定 Resources 为 `false`。 - -!> 确保目标替换 Resources 的类型为 `Boolean`。 - -#### replaceToModuleResource *- method* - -```kotlin -fun replaceToModuleResource(resId: Int) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 替换为当前 Xposed 模块的 Resources。 - -你可以直接使用模块的 `R.string.xxx`、`R.mipmap.xxx`、`R.drawable.xxx` 替换 Hook APP 的 Resources。 - -**功能示例** - -使用此方法可非常方便地使用当前模块的 Resources 去替换目标 Hook APP 的 Resources。 - -这个过程你无需对目标 Resources 实现 `fwd` 方法。 - -比如我们要替换一个字符串。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "test_string" - string() - } - replaceToModuleResource(R.string.module_string) -} -``` - -还可以替换一些复杂的 Resources,比如 `xml` 创建的 `drawable`。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "test_drawable" - drawable() - } - replaceToModuleResource(R.drawable.module_drawable) -} -``` - -#### injectAsLayout *- method* - -```kotlin -fun injectAsLayout(initiate: YukiResources.LayoutInflatedParam.() -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 作为装载的布局注入。 - -**功能示例** - -你可以直接注入一个布局监听并修改它的内部 `View`。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "activity_main" - layout() - } - injectAsLayout { - findViewByIdentifier(name = "test_view")?.isVisible = false - findViewByIdentifier(name = "test_text_view")?.text = "Hook this" - } -} -``` - -你还可以通过 `currentView` 拿到 `Context`。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "activity_main" - layout() - } - injectAsLayout { - Toast.makeText(currentView.context, "Hook this", Toast.LENGTH_SHORT).show() - } -} -``` - -#### ConditionFinder *- class* - -```kotlin -inner class ConditionFinder internal constructor() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> Resources 查找条件实现类。 - -##### name *- field* - -```kotlin -var name: String -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 名称。 - -##### anim *- method* - -```kotlin -fun anim() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为动画。 - -##### animator *- method* - -```kotlin -fun animator() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为属性动画。 - -##### bool *- method* - -```kotlin -fun bool() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为布朗(Boolean)。 - -##### color *- method* - -```kotlin -fun color() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为颜色(Color)。 - -##### dimen *- method* - -```kotlin -fun dimen() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为尺寸(Dimention)。 - -##### drawable *- method* - -```kotlin -fun drawable() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为 Drawable。 - -##### integer *- method* - -```kotlin -fun integer() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为整型(Integer)。 - -##### layout *- method* - -```kotlin -fun layout() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为布局(Layout)。 - -##### plurals *- method* - -```kotlin -fun plurals() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为 Plurals。 - -##### string *- method* - -```kotlin -fun string() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为字符串(String)。 - -##### xml *- method* - -```kotlin -fun xml() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为 Xml。 - -##### mipmap *- method* - -```kotlin -fun mipmap() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 Resources 类型为位图(Mipmap)。 - -##### array *- method* - -```kotlin -fun array() -``` - -**变更记录** - -`v1.1.0` `新增` - -**功能描述** - -> 设置 Resources 类型为数组(Array)。 - -#### Result *- class* - -```kotlin -inner class Result internal constructor() -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 监听全部 Hook 结果实现类,可在这里处理失败事件监听。 - -##### result *- method* - -```kotlin -inline fun result(initiate: Result.() -> Unit): Result -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 创建监听事件方法体。 - -##### by *- method* - -```kotlin -inline fun by(condition: () -> Boolean): Result -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 添加执行 Hook 需要满足的条件,不满足条件将直接停止 Hook。 - -#### onHookingFailure *- method* - -```kotlin -fun onHookingFailure(result: (Throwable) -> Unit): Result -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 监听 Hook 过程发生错误的回调方法。 - -#### ignoredHookingFailure *- method* - -```kotlin -fun ignoredHookingFailure(): Result -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 忽略 Hook 过程出现的错误。 \ No newline at end of file diff --git a/docs/api/public/YukiXposedEvent.md b/docs/api/public/YukiXposedEvent.md deleted file mode 100644 index 3751a792..00000000 --- a/docs/api/public/YukiXposedEvent.md +++ /dev/null @@ -1,69 +0,0 @@ -## YukiXposedEvent *- object* - -```kotlin -object YukiXposedEvent -``` - -**变更记录** - -`v1.0.80` `添加` - -**功能描述** - -> 实现对原生 Xposed API 的装载事件监听。 - -### events *- method* - -```kotlin -inline fun events(initiate: YukiXposedEvent.() -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 对 `YukiXposedEvent` 创建一个方法体。 - -### onInitZygote *- method* - -```kotlin -fun onInitZygote(result: (StartupParam) -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 initZygote 事件监听。 - -### onHandleLoadPackage *- method* - -```kotlin -fun onHandleLoadPackage(result: (LoadPackageParam) -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 handleLoadPackage 事件监听。 - -### onHandleInitPackageResources *- method* - -```kotlin -fun onHandleInitPackageResources(result: (InitPackageResourcesParam) -> Unit) -``` - -**变更记录** - -`v1.0.80` `新增` - -**功能描述** - -> 设置 handleInitPackageResources 事件监听。 \ No newline at end of file diff --git a/docs/config/api-example.md b/docs/config/api-example.md deleted file mode 100644 index a84ed983..00000000 --- a/docs/config/api-example.md +++ /dev/null @@ -1,366 +0,0 @@ -# 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 -object CustomHooker : YukiBaseHooker() { - - override fun onHook() { - // Your code here. - } -} -``` - -子 Hooker **建议使用**单例 `object` 创建,你也可以使用 `class` 但不推荐。 - -!> 你无需再在继承于 `YukiBaseHooker` 的 `onHook` 方法中重新调用 `encase`,这是错误的,你应该直接开始编写你的 Hook 代码。 - -> 示例如下 - -```kotlin -object 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 HookEntry : IYukiHookXposedInit { - - override fun onHook() = encase { - loadApp(name = "com.example.demo", ChildCustomHooker) - } -} - -object ChildCustomHooker : YukiBaseHooker() { - - override fun onHook() { - findClass(name = "$packageName.DemoClass").hook { - // Your code here. - } - } -} -``` - -你可以使用 `loadHooker` 方法在子 Hooker 中多层装载另一个 Hooker,请按照你的喜好进行即可。 - -> 示例如下 - -```kotlin -object FirstHooker : YukiBaseHooker() { - - override fun onHook() { - findClass(name = "$packageName.DemoClass").hook { - // Your code here. - } - loadHooker(SecondHooker) - loadHooker(ThirdHooker) - } -} -``` - -搭建完全部 Hooker 后,你就可以在你的 `HookEntryClass` 入口类中的 `onHook` 方法中装载你的 Hooker 了。 - -> 示例如下 - -```kotlin -class HookEntry : IYukiHookXposedInit { - - override fun onHook() = - YukiHookAPI.encase(FirstHooker, SecondHooker, ThirdHooker ...) -} -``` - -当然,我们同样可以对其进行简写。 - -> 示例如下 - -```kotlin -class HookEntry : IYukiHookXposedInit { - - override fun onHook() = encase(FirstHooker, SecondHooker, ThirdHooker ...) -} -``` - -### 扩展特性 - -如果你当前使用的 Hook Framework 支持并启用了资源钩子(Resources Hook)功能,你现在可以直接在 `encase` 中创建 Resources Hook。 - -你完全不需要与之前在使用 Xposed API 那样区分 `initZygote`、`handleLoadPackage`、`handleInitPackageResources` 方法来执行不同的功能。 - -在 `YukiHookAPI` 中,这些功能**是无缝的**。 - -> 示例如下 - -```kotlin -encase { - loadApp(name = "com.example.demo") { - findClass(name = "$packageName.DemoClass").hook { - // Your code here. - } - // 创建一个 Resources Hook (固定用法) - resources().hook { - // Your code here. - } - } -} -``` - -你还可以同时使用 `loadZygote` 方法来装载新的进程被 fork 后的第一个事件 `initZygote`。 - -> 示例如下 - -```kotlin -encase { - loadZygote { - ActivityClass.hook { - // Your code here. - } - // 在 Zygote 中创建 Resources Hook - resources().hook { - // Your code here. - } - } - loadApp(name = "com.example.demo") { - findClass(name = "$packageName.DemoClass").hook { - // Your code here. - } - // 在 APP 中创建 Resources Hook - resources().hook { - // Your code here. - } - } -} -``` - -### 注意事项 - -!> 无论使用 `encase` 创建 `lambda` 方法体还是直接使用 Hooker 形式,你都不应该直接在首个 `onHook` 事件中直接装载 Hooker 或直接开始 Hook。 - -直接装载 Hooker 或直接开始 Hook 是错误的,`encase` 事件在被 Hook Framework 装载后,会经历三次回调。 - -- 装载 `initZygote` → `encase` - -- 装载 `handleLoadPackage` → `encase` - -- 装载 `handleInitPackageResources` → `encase` - -在这个过程中,你需要使用 `loadApp`、`loadSystem`、`loadZygote` 来区分每一次装载代码的调用域,否则你的代码就会被**多次执行造成错误**。 - -下面是两个**错误**示例。 - -> 示例代码 1 - -```kotlin -encase { - // ❗错误的使用方法,不能直接装载 Hooker - loadHooker(CustomHooker) - // ❗错误的使用方法,不能直接开始 Hook - findClass(name = "com.example.demo.DemoClass").hook { - // ... - } - // ❗错误的使用方法,不能直接开始 Hook - resources().hook { - // ... - } -} -``` - -> 示例代码 2 - -```kotlin -class HookEntry : IYukiHookXposedInit { - - override fun onHook() { - encase(CustomHooker) - } -} - -object CustomHooker : YukiBaseHooker() { - - override fun onHook() { - // ❗错误的使用方法,由于外层没有任何判断对象,不能直接开始 Hook - findClass(name = "com.example.demo.DemoClass").hook { - // ... - } - } -} -``` - -下面是上述错误示例的**正确**示例。 - -> 示例代码 1 - -```kotlin -encase { - // ✅ 正确的使用方法,在 Zygote 中装载 - loadZygote(CustomHooker) - // ✅ 正确的使用方法,在 Zygote 中装载 - loadZygote { - // ✅ 正确的使用方法,在 Zygote 内 Hook - resources().hook { - // ... - } - } - // ✅ 正确的使用方法,使用 APP 作用域装载 - loadApp(/** name 参数可选 */, hooker = CustomHooker) - // ✅ 正确的使用方法,判断 APP 作用域后再装载 Hooker - loadApp(/** name 参数可选 */) { - loadHooker(CustomHooker) - // ✅ 正确的使用方法,在 APP 作用域内 Hook - findClass(name = "com.example.demo.DemoClass").hook { - // ... - } - // ✅ 正确的使用方法,在 APP 作用域内 Hook - resources().hook { - // ... - } - } -} -``` - -> 示例代码 2 - -```kotlin -class HookEntry : IYukiHookXposedInit { - - override fun onHook() { - encase(CustomHooker) - } -} - -object CustomHooker : YukiBaseHooker() { - - override fun onHook() { - // ✅ 正确的使用方法,由于外层没有任何判断对象,需要判断 APP 作用域后再进行 Hook - loadApp(/** name 参数可选 */) { - findClass(name = "com.example.demo.DemoClass").hook { - // ... - } - } - } -} -``` - -## 作为 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 将完全不工作。 - -!> Resources Hook 功能不支持作为 Hook API 使用。 - -

-[浏览下一篇  ➡️](config/api-exception.md) \ No newline at end of file diff --git a/docs/config/api-exception.md b/docs/config/api-exception.md deleted file mode 100644 index 9f07eabf..00000000 --- a/docs/config/api-exception.md +++ /dev/null @@ -1,1302 +0,0 @@ -# API 异常处理 - -> 异常是在开发过程经常遇到的主要问题,这里介绍了 `YukiHookAPI` 在使用过程中可能遇到的常见异常以及处理方式。 - -这里的异常说明只会同步最新的 API 版本,较旧的 API 版本的异常将不会再进行说明,请始终保持 API 版本为最新。 - -## 非阻断异常 - -> 这些异常不会导致 APP 停止运行(FC),但是会在控制台打印 `E` 级别的日志,也可能会停止继续执行相关功能。 - -!> `loggerE` Could not found XposedBridge in current space! Aborted - -**异常原因** - -你的 Hook Framework 未在工作或并未成功装载 `XposedBridge`。 - -**解决方案** - -请确认你在正确的地方装载了 `YukiHookAPI` 的 `encase` 方法,详情请参考 [作为 Xposed 模块使用的相关配置](config/xposed-using) 以及 [作为 Hook API 使用的相关配置](config/api-using)。 - -!> `loggerE` You cannot load a hooker in "onInit" or "onXposedEvent" method! Aborted - -**异常原因** - -你尝试在继承 `IYukiHookXposedInit` 的 Hook 入口类的 `onInit` 或 `onXposedEvent` 方法中装载了 `encase` 方法。 - -> 示例如下 - -```kotlin -class HookEntry : IYukiHookXposedInit { - - override fun onInit() { - // ❗错误的使用方法 - YukiHookAPI.encase { - // Your code here. - } - } - - override fun onXposedEvent() { - // ❗错误的使用方法 - YukiHookAPI.encase { - // Your code here. - } - } - - override fun onHook() { - // Your code here. - } -} -``` - -**解决方案** - -请在 `onHook` 方法中装载 `encase` 方法。 - -> 示例如下 - -```kotlin -class HookEntry : IYukiHookXposedInit { - - override fun onInit() { - // 这里只能装载 configs 方法 - YukiHookAPI.configs { - // Your code here. - } - } - - override fun onHook() { - // ✅ 正确的使用方法 - YukiHookAPI.encase { - // Your code here. - } - } -} -``` - -!> `loggerE` Hooking Process exception occurred - -**异常原因** - -`YukiHookAPI` 在进行自身初始化 Hook 过程中发生异常。 - -**解决方案** - -通常情况下这种错误不会轻易发生,若一旦发生此错误,可直接提交日志进行反馈。 - -!> `loggerE` YukiHookAPI try to load HookEntryClass failed - -**异常原因** - -`YukiHookAPI` 在尝试装载 Hook 入口类 `onInit` 或 `onHook` 方法时发生了不能处理的异常或找不到入口类。 - -**解决方案** - -通常情况下这种错误不会轻易发生,若一旦发生此错误,请自行查看控制台打印的日志定位问题,确定并非自己的代码发生的问题后,可提交日志进行反馈。 - -!> `loggerE` YukiHookAPI bind initZygote failed - -**异常原因** - -`YukiHookAPI` 在尝试装载 Xposed 原生接口 `initZygote` 方法时发生了不能处理的异常。 - -**解决方案** - -通常情况下这种错误不会轻易发生,若一旦发生此错误,请自行查看控制台打印的日志定位问题,确定并非自己的代码发生的问题后,可提交日志进行反馈。 - -!> `loggerE` Failed to execute method "**NAME**", maybe your Hook Framework not support Resources Hook - -**异常原因** - -`YukiHookAPI` 在尝试进行 Resources Hook 时发生错误。 - -**解决方案** - -通常这种情况不会发生,请仔细检查错误日志的详细信息,若发生 `ClassNotFound` 等情况可能是 Hook Framework 不支持 Resources Hook(资源钩子)。 - -!> `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` Hooked method return type match failed, required \[**TYPE**\] but got \[**TYPE**\] - -**异常原因** - -在 Hook 回调方法体中设置了 `HookParam.result` 或使用了 `replaceHook` 但是被 Hook 的方法返回值类型与原返回值类型不匹配。 - -> 示例如下 - -假设这个是被 Hook 的方法。 - -```java -private boolean test() -``` - -下面是一个错误的案列。 - -```kotlin -injectMember { - method { - name = "test" - emptyParam() - } - // <情景1> 设置了错误的类型,原类型为 Boolean - beforeHook { - result = 0 - } - // <情景2> 返回了错误的类型,原类型为 Boolean - replaceAny { - 0 - } - // <情景3> 直接使用了错误的类型,原类型为 Boolean - replaceTo(any = 0) -} -``` - -!> 若上述场景在 `beforeHook` 或 `afterHook` 中发生,则会造成被 Hook 的 APP (宿主) 由 `XposedBridge` 抛出异常。 - -**解决方案** - -请确认当前被 Hook 方法的正确返回值类型,修改后再试一次。 - -!> `loggerE` Hook initialization failed because got an Exception - -**异常原因** - -在准备 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` You must set a condition when finding a Method/Constructor/Field - -**异常原因** - -在查找方法、构造方法以及变量时并未设置任何条件。 - -> 示例如下 - -```kotlin -method { - // 这里没有设置任何条件 -} -``` - -**解决方案** - -请将查询条件补充完整并再试一次。 - -!> `loggerE` Can't find this Method/Constructor/Field in \[**CLASS**\]: **CONTENT** Generated by YukiHookAPI#ReflectionTool - -**异常原因** - -通过指定条件找不到需要查找的方法、构造方法以及变量。 - -> 示例如下 - -```kotlin -TargetClass.method { - name = "test" - param(BooleanType) -} -``` - -**解决方案** - -这是一个安全异常,请检查你设置的条件,使用相关工具查看所在 `Class` 中的字节码对象特征,并再试一次。 - -!> `loggerE` The number of VagueType must be at least less than the count of paramTypes - -**异常原因** - -在 `Method`、`Constructor` 查找条件中错误地使用了 `VagueType`。 - -> 示例如下 - -```kotlin -TargetClass.method { - name = "test" - // <情景1> - param(VagueType) - // <情景2> - param(VagueType, VagueType ...) -} -``` - -**解决方案** - -`VagueType` 不能在方法、构造方法参数中完全填充,若存在这样的需求请使用 `paramCount`。 - -!> `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` 是否存在,然后再试一次。 - -!> `loggerE` Resources Hook condition name/type cannot be empty \[**TAG**\] - -**异常原因** - -在查找 Resources 时并未设置任何条件。 - -> 示例如下 - -```kotlin -// 情况 1 -conditions { - // 这里没有设置任何条件 -} -// 情况 2 -conditions { - name = "test" - // 这里缺少了 type 条件 -} -``` - -**解决方案** - -Resources 的 Hook 并非类似方法的 Hook,其必须拥有完整的名称和类型描述才能查询成功,请将查询条件补充完整并再试一次。 - -!> `loggerE` Resources Hook type is invalid \[**TAG**\] - -**异常原因** - -在 Hook Resources 时发生了类型错误的异常。 - -**解决方案** - -`YukiHookAPI` 会尝试在 `initZygote` 与 `handleInitPackageResources` 中装载 Resources Hook,若全部装载失败可能会发生此异常,当前 Hook Framework 需要支持并启用资源钩子(Resources Hook)功能,请检查后再试一次。 - -!> `loggerE` Resources Hook got an Exception \[**TAG**\] - -**异常原因** - -在 Hook Resources 时发生了任意的异常。 - -**解决方案** - -这是一个异常汇总,请自行向下查看日志具体的异常是什么,例如找不到 Resources Id 的问题。 - -!> `loggerE` Received action "**ACTION**" failed - -**异常原因** - -使用 `YukiHookDataChannel` 时回调广播事件异常。 - -**解决方案** - -一般情况下,此错误基本上不会发生,一旦发生错误,排除自身代码的问题后,请携带详细日志进行反馈。 - -!> `loggerE` Failed to sendBroadcast like "**KEY**", because got null context in "**PACKAGENAME**" - -**异常原因** - -使用 `YukiHookDataChannel` 时发送广播取到了空的上下文实例。 - -**解决方案** - -一般情况下,此错误基本上不会发生,在最新版本中已经修复宿主使用时可能发生的问题,若最新版本依然发生错误,排除自身代码的问题后,请携带详细日志进行反馈。 - -!> `loggerE` Failed to inject module resources into \[**RESOURCES**\] - -**异常原因** - -在 (Xposed) 宿主环境中使用 `injectModuleAppResources` 注入模块资源时发生异常。 - -**解决方案** - -一般情况下,此错误基本上不会发生,排除自身代码的问题后,请携带详细日志进行反馈。 - -!> `loggerE` Activity Proxy initialization failed because got an Exception - -**异常原因** - -在 (Xposed) 宿主环境中使用 `registerModuleAppActivities` 注入模块 `Activity` 时发生异常。 - -**解决方案** - -请检查此错误发生后的下一个错误日志,或许在配置参数上可能发生了一些问题,若找不到相关错误日志的说明,排除自身代码的问题后,请携带详细日志进行反馈。 - -!> `loggerE` Activity Proxy got an Exception in msg.what \[**WHAT**\] - -**异常原因** - -在 (Xposed) 宿主环境中使用 `registerModuleAppActivities` 注入模块 `Activity` 时发生异常。 - -**解决方案** - -一般情况下,此错误基本上不会发生,但根据系统版本差异性并未做详细测试,排除自身代码的问题后,请携带详细日志进行反馈。 - -!> `loggerE` This proxy \[**TYPE**\] type is not allowed - -**异常原因** - -在 (Xposed) 宿主环境中使用 `registerModuleAppActivities` 注入模块 `Activity` 时填入了无效的参数。 - -> 示例如下 - -```kotlin -// ❗ 这里填入的内容仅为举例,其中 proxy 填入了不能理解的无效参数 -registerModuleAppActivities(proxy = false) -``` - -**解决方案** - -方法中的 `proxy` 参数只接受 `String`、`CharSequence`、`Class` 类型,请查看相关使用方法正确填入方法参数。 - -!> `loggerE` Cound not got launch intent for package "**NAME**" - -**异常原因** - -在 (Xposed) 宿主环境中使用 `registerModuleAppActivities` 注入模块 `Activity` 时找不到宿主的启动 `Activity`。 - -> 示例如下 - -```kotlin -// 使用了默认参数直接进行注册 -registerModuleAppActivities() -``` - -**解决方案** - -默认参数 (无参) 只能用于可被启动的 APP,若 APP 并未声明启动入口 `Activity`,你就需要手动指定方法的 `proxy` 参数。 - -!> `loggerE` Could not found "**NAME**" or Class is not a type of Activity - -**异常原因** - -在 (Xposed) 宿主环境中使用 `registerModuleAppActivities` 注入模块 `Activity` 时无法找到被填入参数 `proxy` 的 `Activity`。 - -> 示例如下 - -```kotlin -registerModuleAppActivities(proxy = "com.demo.test.TestActivity") -``` - -**解决方案** - -请确认你填入的 `Activity` 名称真实有效地存在于宿主中,且目标 `Class` 继承于 `Activity`。 - -## 阻断异常 - -> 这些异常会直接导致 APP 停止运行(FC),同时会在控制台打印 `E` 级别的日志,还会造成 Hook 进程“死掉”。 - -!> `RuntimeException` !!!DO NOT ALLOWED!!! You cannot hook or reflection to call the internal class of the YukiHookAPI itself, The called class is \[**CLASS**\] - -**异常原因** - -你使用 `YukiHookAPI` 的相关反射或 Hook 功能调用了 API 自身的 `Class` 对象。 - -> 示例如下 - -```kotlin -// <情景1> -YukiHookAPI.current() -// <情景2> -PackageParam::class.java.hook { - // ... -} -// <情景3> -MethodFinder::class.java.method { - name = "name" - param(StringType) -}.get().call("name") -// ... -``` - -**解决方案** - -请检查代码部分是否有错误,例如下面的情况。 - -> 示例如下 - -```kotlin -YourClass.method { - // ... - // ❗ 没有调用方法执行,这里实际调用的是 MethodFinder.Result 对象 -}.get(instance).current() -YourClass.method { - // ... - // ✅ 正确的使用方法,假设此方法无参 -}.get(instance).call().current() -``` - -不允许内联、反射、Hook `YukiHookAPI` 自身的 `Class` 以及内部功能,防止发生错误。 - -!> `UnsupportedOperationException` !!!DANGEROUS!!! Hook \[**CLASS**\] Class is a dangerous behavior! \[**CONTENT**\] \[**SOLVE**\] - -**异常原因** - -你尝试 Hook 了处于危险行为列表中的 `Class` 对象,例如 `Class`、`ClassLoader`、`Method`。 - -> 示例如下 - -```kotlin -// <情景1> -JavaClassLoader.hook { - // ... -} -// <情景2> -JavaClass.hook { - // ... -} -// <情景3> -JavaMethod.hook { - // ... -} -// ... -``` - -**解决方案** - -这些功能是系统内部的,**它们不应该被 Hook,在部分 Hook Framework 上可能不被支持,还会引发其它错误**,请尝试更换 Hook 点。 - -若你仍要使用此功能,请参考 [useDangerousOperation](api/document?id=usedangerousoperation-method)。 - -> 示例如下 - -```kotlin -JavaClassLoader.hook { - useDangerousOperation(option = "Yes do as I say!") - injectMember { - method { - // ... - } - afterHook { - // ... - } - } -} -``` - -这只是一个用于调试的功能,**强烈建议不要这样做,发生问题请不要反馈,自行承担一切后果**。 - -!> `NoClassDefFoundError` Can't find this Class in \[**CLASSLOADER**\]: **CONTENT** Generated by YukiHookAPI#ReflectionTool - -**异常原因** - -通过 `String.toClass(...)` 或 `classOf<...>()` 找不到需要查找的 `Class` 对象。 - -> 示例如下 - -```kotlin -"com.demo.Test".toClass() -``` - -**解决方案** - -请检查当前字符串或实体匹配到的 `Class` 是否存在于当前 `ClassLoader`,并再试一次。 - -!> `IllegalStateException` Failed to got SystemContext - -**异常原因** - -在被 Hook 的宿主内调用了 `systemContext` 但并未成功获取到实例对象。 - -> 示例如下 - -```kotlin -encase { - // 调用了此变量 - systemContext... -} -``` - -**解决方案** - -这种情况不应该存在,由于 `systemContext` 通过反射从 `ActivityThread` 中得到,除非系统进程发生异常,否则获取到的对象不会为空。 - -!> `IllegalStateException` App is dead, You cannot call to appContext - -**异常原因** - -> 第一种情况 - -在被 Hook 的宿主内调用了 `ModuleApplication` 的 `appContext`。 - -> 示例如下 - -```kotlin -encase { - // 调用了此变量 - ModuleApplication.appContext... -} -``` - -> 第二种情况 - -使用 `ModuleApplication` 时调用了 `appContext` 但是 APP 可能已经被销毁或没有正确启动。 - -> 示例如下 - -```kotlin -// 调用了此变量但是 APP 可能已被销毁或没有正确启动 -ModuleApplication.appContext... -``` - -**解决方案** - -> 第一种情况 - -你只能在模块内使用 `ModuleApplication` 的 `appContext`,在宿主内请使用 `PackageParam` 中的 `appContext`,请确认你使用的是否正确。 - -> 第二种情况 - -这种情况基本不存在,由于 `appContext` 是在 `onCreate` 中被赋值的,除非遇到多进程并发启动或 APP 没有启动完成前被反射调用了父类的 `onCreate` 方法。 - -!> `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` Cannot load the XSharedPreferences, maybe is your Hook Framework not support it - -**异常原因** - -在 (Xposed) 宿主环境使用了 `YukiHookModulePrefs` 但是无法得到 `XSharedPreferences` 对象。 - -> 示例如下 - -```kotlin -encase { - prefs... // 调用了此变量 -} -``` - -**解决方案** - -一般情况下不会发生此问题,若持续无法获取 `XSharedPreferences` 对象则可能是你使用的 Hook Framework 不支持此功能或自身存在错误。 - -!> `IllegalStateException` YukiHookDataChannel not allowed in Custom Hook API - -**异常原因** - -在 Hook 自身 APP(非 Xposed 模块) 中使用了 `YukiHookDataChannel`。 - -> 示例如下 - -```kotlin -class MyApplication : Application() { - - override fun attachBaseContext(base: Context?) { - YukiHookAPI.encase(base) { - // ❗不能在这种情况下使用 dataChannel - dataChannel.wait(key = "test_data") { - // ... - } - } - super.attachBaseContext(base) - } -} -``` - -**解决方案** - -你只能在 [作为 Xposed 模块使用](config/xposed-using) 时使用 `YukiHookDataChannel`。 - -!> `IllegalStateException` YukiHookDataChannel only support used on an Activity, but this current context is "**CLASSNAME**" - -**异常原因** - -在模块的非 `Activity` 环境中使用了 `YukiHookDataChannel`。 - -**解决方案** - -你只能在 `Activity` 或 `Fragment` 中使用 `YukiHookDataChannel`。 - -!> `IllegalStateException` Xposed modulePackageName load failed, please reset and rebuild it - -**异常原因** - -在 Hook 过程中使用 `YukiHookModulePrefs` 或 `YukiHookDataChannel` 时无法读取装载时的 `modulePackageName` 导致不能确定自身模块的包名。 - -**解决方案** - -请仔细阅读 [这里](config/xposed-using?id=modulepackagename-参数) 的帮助文档,正确配置模块的 Hook 入口类包名。 - -!> `IllegalStateException` YukiHookModulePrefs missing Context instance - -**异常原因** - -在模块中使用了 `YukiHookModulePrefs` 存储数据但并未传入 `Context` 实例。 - -> 示例如下 - -```kotlin -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // ❗错误的使用方法 - // 构造方法已在 API 1.0.88 及以后的版本中设置为 private - YukiHookModulePrefs().getBoolean("test_data") - } -} -``` - -**解决方案** - -在 `Activity` 中推荐使用 `modulePrefs` 方法来装载 `YukiHookModulePrefs`。 - -> 示例如下 - -```kotlin -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // ✅ 正确的使用方法 - modulePrefs.getBoolean("test_data") - } -} -``` - -!> `IllegalStateException` Key-Value type **TYPE** is not allowed - -**异常原因** - -在使用 `YukiHookModulePrefs` 的 `get` 或 `put` 方法或 `YukiHookDataChannel` 的 `wait` 或 `put` 方法时传入了不支持的存储类型。 - -**解决方案** - -`YukiHookModulePrefs` 支持的类型只有 `String`、`Set`、`Int`、`Float`、`Long`、`Boolean`,请传入支持的类型。 - -`YukiHookDataChannel` 支持的类型为 `Intent.putExtra` 限制的类型,请传入支持的类型。 - -!> `IllegalStateException` YukiHookDataChannel cannot used in zygote - -**异常原因** - -在 `loadZygote` 中使用了 `YukiHookDataChannel`。 - -> 示例如下 - -```kotlin -loadZygote { - // 调用了此变量 - dataChannel... -} -``` - -**解决方案** - -`YukiHookDataChannel` 只能在 `loadSystem`、`loadApp` 中使用。 - -!> `IllegalStateException` Custom Hooking Members is empty - -**异常原因** - -在 `MemberHookCreator` 中调用 `members()` 但是未设置需要 Hook 的 `Member` 实例。 - -> 示例如下 - -```kotlin -injectMember { - // 括号里的方法参数被留空了 - members() - afterHook { - // ... - } -} -``` - -**解决方案** - -若要使用 `members()` 设置自定义 Hook 方法,你必须保证其方法参数里的 `Member` 数组对象不能为空。 - -!> `IllegalStateException` HookParam Method args index must be >= 0 - -**异常原因** - -在 `HookParam` 中调用 `args().last()` 但是目标 `param` 为空或 `args` 中的 `index` 设置了小于 0 的数值。 - -> 示例如下 - -```kotlin -injectMember { - // ... - afterHook { - // 假设 param 是空的 - args().last()... - // 设置了小于 0 的 index - args(index = -5)... - } -} -``` - -**解决方案** - -请确认你 Hook 的目标方法、构造方法的方法参数数量是否不为空,且不能对 `args` 的下标设置小于 0 的数值。 - -!> `IllegalStateException` HookParam instance got null! Is this a static member? - -**异常原因** - -在 `HookParam` 中调用 `instance` 变量或 `instance` 方法但获取不到当前实例的对象。 - -> 示例如下 - -```kotlin -injectMember { - // ... - afterHook { - // 调用了此变量 - instance... - // 调用了此方法 - instance()... - } -} -``` - -**解决方案** - -请确认你 Hook 的方法是否为静态类型,静态类型的方法没有实例,不能使用此功能,若非静态方法,请检查实例是否已经销毁。 - -!> `IllegalStateException` Current hooked Member args is null - -**异常原因** - -在 `HookParam` 中调用 `args` 变量但获取不到当前实例方法、构造方法的参数数组。 - -> 示例如下 - -```kotlin -injectMember { - // ... - afterHook { - // 调用了此变量 - args... - } -} -``` - -**解决方案** - -这种问题一般不会发生,真的发生了此问题,请携带详细日志进行反馈。 - -!> `IllegalStateException` Current hooked Member is null - -**异常原因** - -在 `HookParam` 中调用 `member` 变量但获取不到当前实例的方法、构造方法实例。 - -> 示例如下 - -```kotlin -injectMember { - // ... - afterHook { - // 调用了此变量 - member... - } -} -``` - -**解决方案** - -这种问题一般不会发生,真的发生了此问题,请携带详细日志进行反馈。 - -!> `IllegalStateException` Current hooked Member is not a Method - -**异常原因** - -在 `HookParam` 中调用 `method` 变量但获取不到当前实例的方法实例。 - -> 示例如下 - -```kotlin -injectMember { - // ... - afterHook { - // 调用了此变量 - method... - } -} -``` - -**解决方案** - -请确认你 Hook 的方法是构造方法还是普通方法并使用对应类型的方法获取指定的实例,若不知道字节码的类型可以直接使用 `member` 来获取。 - -!> `IllegalStateException` Current hooked Member is not a Constructor - -**异常原因** - -在 `HookParam` 中调用 `constructor` 变量但获取不到当前实例的方法实例。 - -> 示例如下 - -```kotlin -injectMember { - // ... - afterHook { - // 调用了此变量 - constructor... - } -} -``` - -**解决方案** - -请确认你 Hook 的方法是普通方法还是构造方法并使用对应类型的方法获取指定的实例,若不知道字节码的类型可以直接使用 `member` 来获取。 - -!> `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` Current Hook Framework not support moduleAppResources - -**异常原因** - -在 `PackageParam` 中调用了 `moduleAppResources` 变量但是无法获取到实例对象。 - -> 示例如下 - -```kotlin -encase { - // 调用了此变量 - moduleAppResources... -} -``` - -**解决方案** - -`moduleAppResources` 需要当前 Hook Framework 支持 `initZygote` 功能,请检查后再试一次。 - -!> `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` LayoutInflatedParam View instance got null - -**异常原因** - -在布局 Hook 回调中调用了 `currentView` 但没取到实例对象。 - -> 示例如下 - -```kotlin -injectResource { - conditions { - name = "activity_main" - layout() - } - injectAsLayout { - // 调用了此变量 - currentView... - } -} -``` - -**解决方案** - -这种情况基本上不存在,除非被 Hook 的宿主当前 `Activity` 已经销毁或 Hook Framework 自身存在问题。 - -!> `IllegalStateException` XResForwarder is invalid - -**异常原因** - -在 `YukiResForwarder` 中调用了 `resources` 但没取到实例对象。 - -> 示例如下 - -```kotlin -// 调用了此变量 -moduleAppResources.fwd(...).resources -``` - -**解决方案** - -这种情况基本上不存在,除非 Hook Framework 自身存在问题。 - -!> `IllegalStateException` paramTypes is empty, please use emptyParam() instead - -**异常原因** - -在查找方法、构造方法时保留了空的 `param` 方法。 - -> 示例如下 - -```kotlin -method { - name = "test" - // 括号内没有填写任何参数 - param() -} -``` - -**解决方案** - -若要标识此方法、构造方法没有参数,你可以有如下设置方法。 - -第一种,设置 `emptyParam` (推荐) - -> 示例如下 - -```kotlin -method { - name = "test" - emptyParam() -} -``` - -第二种,设置 `paramCount = 0` - -> 示例如下 - -```kotlin -method { - name = "test" - paramCount = 0 -} -``` - -!> `IllegalStateException` Invalid YukiHookCallback type - -**异常原因** - -`YukiHookAPI` 的核心 Hook 功能发生故障。 - -**解决方案** - -这种情况基本上不存在,若发生上述问题,确定并非自己的代码发生的问题后,可提交日志进行反馈。 - -!> `IllegalStateException` ModuleContextThemeWrapper already loaded - -**异常原因** - -在 `Context` 中使用 `applyTheme` 方法时重复进行调用。 - -> 示例如下 - -```kotlin -// 假设这就是当前的 Context 对象 -context.applyTheme(R.style.Theme_AppCompat).applyTheme(R.style.Theme_AppCompat) -``` - -**解决方案** - -在 `Context` 中只能创建一次 `ModuleContextThemeWrapper`,请检查代码是否有循环调用问题。 - -

-[浏览下一篇  ➡️](config/xposed-using.md) \ No newline at end of file diff --git a/docs/config/api-using.md b/docs/config/api-using.md deleted file mode 100644 index 9dd32cbb..00000000 --- a/docs/config/api-using.md +++ /dev/null @@ -1,111 +0,0 @@ -# 作为 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) -} -``` - -

-[浏览下一篇  ➡️](config/r8-proguard.md) \ No newline at end of file diff --git a/docs/config/r8-proguard.md b/docs/config/r8-proguard.md deleted file mode 100644 index 22f1de4f..00000000 --- a/docs/config/r8-proguard.md +++ /dev/null @@ -1,22 +0,0 @@ -# R8 与 Proguard 混淆 - -> 大部分场景下 Xposed 模块可通过原生混淆压缩体积,这里介绍了混淆的配置方法。 - -## R8 - -> 如果你使用的是 `R8`,那么你无需对 `YukiHookAPI` 进行任何特殊配置。 - -## Proguard - -> ~~如果你仍然在使用 `Proguard`,你需要做一些规则配置。~~ - -!> Proguard 规则已被弃用,请不要再使用,自从 Android Gradle Plugin 4.2 后,拥有 Android Jetpack 套件最新版本的混淆处理程序默认均为 `R8`,不再需要考虑混淆的问题。 - -若要在任何版本下启用 `R8`,请在 `gradle.properties` 文件中加入如下规则,Android Gradle Plugin 7.0 及以上版本无需任何配置。 - -```gradle -android.enableR8=true -``` - -

-[浏览下一篇  ➡️](tools/yukihookapi-projectbuilder.md) \ No newline at end of file diff --git a/docs/config/xposed-using.md b/docs/config/xposed-using.md deleted file mode 100644 index 9e58ad4f..00000000 --- a/docs/config/xposed-using.md +++ /dev/null @@ -1,255 +0,0 @@ -# 作为 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, - val entryClassName: String, - val isUsingResourcesHook: Boolean -) -``` - -`@InjectYukiHookWithXposed` 注解是一个标记模块 Hook 入口的重要注解。 - -!> `@InjectYukiHookWithXposed` 注解的 `Class` 必须实现 `IYukiHookXposedInit` 接口。 - -!> 在你当前项目中的所有 `Class` 标记中**只能存在一次**,若存在多个声明自动处理程序**会在编译时抛出异常**,你可以自定义其相关参数。 - -#### sourcePath 参数 - -`sourcePath` 参数决定了自动处理程序自动查找并匹配你当前项目路径的重要标识,此参数的内容为相对路径匹配,默认参数为 `src/main`。 - -!> 如果你的项目不在 `../src/main..` 或你手动使用 `sourceSets` 设置了项目路径,你就需要手动设置 `sourcePath` 参数,否则自动处理程序将无法识别你的项目路径并**会在编译时抛出异常**。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed(sourcePath = "src/custom") -``` - -`sourcePath` 使用的文件路径分隔符写法根据 `Windows` 和 `Unix` 将自动进行识别,使用 `/` 或 `\` 均可。 - -#### modulePackageName 参数 - -`modulePackageName` 是你当前项目的 `applicationId`,也就是你的模块包名 (最终生成的应用包名),留空或不填时自动处理程序将对当前项目文件进行分析并生成。 - -!> 若你想使用模块包名自动生成,你需要确保你的项目命名空间在 `AndroidManifest.xml`、`build.gradle` 或 `build.gradle.kts` 中定义。 - -示例命名空间 `com.example.demo`,以下定义方式任选其一。 - -以下定义方式仅供参考,通常情况下**只要你的项目能够正常生成 `BuildConfig.java` 文件,就不需要做额外操作**。 - -- `AndroidManifest.xml` 示例 - -```xml - -``` - -- `build.gradle` 示例 - -```groovy -android { - namespace 'com.example.demo' -} -``` - -- `build.gradle.kts` 示例 - -```kotlin -android { - namespace = "com.example.demo" -} -``` - -若你的模块包名是非常规手段进行自动生成的,或你认为有必要手动定义模块包名,那么你可以直接设置 `modulePackageName` 的参数。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed(modulePackageName = "com.example.demo") -``` - -只要你自定义了 `modulePackageName` 的参数,你就会在编译时收到警告。 - -> 示例如下 - -``` -You set the customize module package name to "com.example.demo", please check for yourself if it is correct -``` - -!> 手动定义的模块包名除了格式之外,自动处理程序将不会再检查模块包名是否正确,需要你自行确认其有效性。 - -#### entryClassName 参数 - -`entryClassName` 决定了自动处理程序如何生成 `xposed_init` 中的入口类名,默认会使用你的入口类包名插入 `_YukiHookXposedInit` 后缀进行生成。 - -假设这是你的入口类。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed -class HookEntry: IYukiHookXposedInit -``` - -Xposed 入口类处理如下。 - -> 示例如下 - -```kotlin -class HookEntry_YukiHookXposedInit: IXposedHookZygoteInit, IXposedHookLoadPackage, ... -``` - -编译后的类名结构如下。 - -> 示例如下 - -``` -...hook.HookEntry ← 你的入口类 -...hook.HookEntry_Impl ← 自动生成的 Impl 类 -...hook.HookEntry_YukiHookXposedInit ← 自动生成的 Xposed 入口类 -``` - -我们现在定义入口类名称为 `HookXposedEntry`。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed(entryClassName = "HookXposedEntry") -class HookEntry: IYukiHookXposedInit -``` - -Xposed 入口类处理如下。 - -> 示例如下 - -```kotlin -class HookXposedEntry: IXposedHookZygoteInit, IXposedHookLoadPackage, ... -``` - -编译后的类名结构如下。 - -> 示例如下 - -``` -...hook.HookEntry ← 你的入口类 -...hook.HookEntry_Impl ← 自动生成的 Impl 类 -...hook.HookXposedEntry ← 自动生成的 Xposed 入口类 -``` - -!> 你定义的 `entryClassName` 不可与 `xposed_init` 中的类名相同,否则自动处理程序会在编译时抛出异常。 - -#### isUsingResourcesHook 参数 - -`isUsingResourcesHook` 决定了自动处理程序是否生成针对 Resources Hook 的相关代码,此功能默认是启用的。 - -启用后生成的入口类将为如下所示。 - -> 示例如下 - -```kotlin -class _YukiHookXposedInit : IXposedHookZygoteInit, IXposedHookLoadPackage, IXposedHookInitPackageResources { - - override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam?) { - // ... - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { - // ... - } - - override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam?) { - // ... - } -} -``` - -若你当前的项目并不需要用到 Reources Hook,可以设置 `isUsingResourcesHook = false` 来关闭自动生成。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed(isUsingResourcesHook = false) -``` - -关闭后生成的入口类将为如下所示。 - -> 示例如下 - -```kotlin -class _YukiHookXposedInit : IXposedHookZygoteInit, IXposedHookLoadPackage { - - override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam?) { - // ... - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { - // ... - } -} -``` - -### IYukiHookXposedInit 接口 - -`IYukiHookXposedInit` 接口为你的 `HookEntryClass` 必须实现的接口,这是你的模块开始 Hook 的起点。 - -若要了解更多可 [点击这里](api/document?id=iyukihookxposedinit-interface) 进行查看。 - -当你的模块被 Xposed 装载后,`onHook` 方法将会被回调,你需要在此方法中开始使用 `YukiHookAPI`。 - -> 基本的调用流程为 `_YukiHookXposedInit` → `IYukiHookXposedInit.onXposedEvent` → `IYukiHookXposedInit.onInit` → `IYukiHookXposedInit.onHook` - -详情请参考 [API 基本配置](config/api-example)。 - -## 原生 Xposed API 事件 - -若你当前的 Xposed 模块使用了第三方的资源,但是短时间内可能无法转移它们,此时,你可以使用 `onXposedEvent` 实现监听原生 Xposed API 的全部装载事件。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed -class HookEntry: IYukiHookXposedInit { - - override fun onHook() { - // Your code here. - } - - override fun onXposedEvent() { - // 监听原生 Xposed API 的装载事件 - YukiXposedEvent.events { - onInitZygote { - // it 对象即 [StartupParam] - } - onHandleLoadPackage { - // it 对象即 [LoadPackageParam] - } - onHandleInitPackageResources { - // it 对象即 [InitPackageResourcesParam] - } - } - } -} -``` - -`onXposedEvent` 与 `onHook` 方法完全独立存在,互不影响,你可以继续在 `onHook` 方法中使用 `YukiHookAPI`。 - -若要了解更多可 [点击这里](api/document?id=onxposedevent-method) 进行查看。 - -

-[浏览下一篇  ➡️](config/api-using.md) \ No newline at end of file diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index f17b73bb..00000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/guide/example.md b/docs/guide/example.md deleted file mode 100644 index 3874885e..00000000 --- a/docs/guide/example.md +++ /dev/null @@ -1,629 +0,0 @@ -# 用法示例 - -> 这里介绍了 `YukiHookAPI` 的基本工作方式以及列举了简单的 Hook 例子和常用功能。 - -## 结构图解 - -> 下方的结构描述了 `YukiHookAPI` 的基本工作方式和原理。 - -``` -Host Environment -└── YukiMemberHookCreator - └── Class - └── MemberHookCreator - └── Member - ├── Before - └── After - MemberHookCreator - └── Member - ├── Before - └── After - ... - YukiResourcesHookCreator - └── Resources - └── ResourcesHookCreator - └── Drawable - └── Replace - ResourcesHookCreator - └── Layout - └── Inject - ... -``` - -> 上方的结构换做代码将可写为如下形式。 - -```kotlin -TargetClass.hook { - injectMember { - method { - // Your code here. - } - beforeHook { - // Your code here. - } - afterHook { - // Your code here. - } - } -} -resources().hook { - injectResource { - conditions { - // Your code here. - } - replaceTo(...) - } -} -``` - -## 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 APP、Hook 系统框架与 Hook Resources 等例子,可供参考。 - -### Hook APP - -假设,我们要 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" - emptyParam() - 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. - } -} -``` - -更多功能请参考 [MemberHookCreator](api/document?id=memberhookcreator-class)。 - -### Hook Zygote - -在 APP 启动时,新的进程被 fork 后的第一个事件 `initZygote`。 - -假设我们要全局 Hook 一个 APP `Activity` 的 `onCreate` 事件 - -在 `encase` 方法体中添加代码。 - -> 示例如下 - -```kotlin -loadZygote { - ActivityClass.hook { - injectMember { - method { - name = "onCreate" - param(BundleClass) - returnType = UnitType - } - afterHook { - // Your code here. - } - } - } -} -``` - -!> 在 `loadZygote` 中进行的功能十分有限,几乎很少的情况下需要用到 `loadZygote` 方法。 - -### Hook 系统框架 - -在 `YukiHookAPI` 中,Hook 系统框架的实现非常简单。 - -假设,你要得到 `ApplicationInfo` 与 `PackageInfo` 并对它们进行一些操作。 - -在 `encase` 方法体中添加代码。 - -> 示例如下 - -```kotlin -loadSystem { - ApplicationInfoClass.hook { - // Your code here. - } - PackageInfoClass.hook { - // Your code here. - } -} -``` - -!> `loadZygote` 与 `loadSystem` 有直接性区别,`loadZygote` 会在 `initZygote` 中装载,系统框架被视为 `loadApp(name = "android")` 而存在,若要 Hook 系统框架,可直接使用 `loadSystem`。 - -### Hook Resources - -假设,我们要 Hook `com.android.browser` 中 `string` 类型的 `app_name` 内容替换为 `123`。 - -在 `encase` 方法体中添加代码。 - -> 示例如下 - -```kotlin -loadApp(name = "com.android.browser") { - resources().hook { - injectResource { - conditions { - name = "app_name" - string() - } - replaceTo("123") - } - } -} -``` - -若当前 APP 使用 `app_name` 设置了标题栏文本,则它就会变成我们的 `123`。 - -你还可以使用当前 Xposed 模块的 Resources 替换 Hook APP 的 Resources。 - -假设,我们要继续 Hook `com.android.browser` 中 `mipmap` 类型的 `ic_launcher`。 - -> 示例如下 - -```kotlin -loadApp(name = "com.android.browser") { - resources().hook { - injectResource { - conditions { - name = "ic_launcher" - mipmap() - } - replaceToModuleResource(R.mipmap.ic_launcher) - } - } -} -``` - -至此目标 APP 的图标将会被替换为我们设置的图标。 - -若你想替换系统框架的资源,同样也可以这样实现,只需要把 `loadApp` 换成 `loadZygote` 即可。 - -> 示例如下 - -```kotlin -loadZygote { - resources().hook { - // Your code here. - } -} -``` - -更多功能请参考 [ResourcesHookCreator](api/document?id=resourceshookcreator-class)。 - -### 解除 Hook - -原生的 Xposed 为我们提供了一个 `XC_MethodHook.Unhook` 功能,可以从 Hook 队列中将当前 Hook 移除,`YukiHookAPI` 同样可以实现此功能。 - -第一种方法,保存当前注入对象的 `Result` 实例,在适当的时候和地方调用 `remove` 即可解除该注入对象。 - -> 示例如下 - -```kotlin -// 设置一个变量保存当前实例 -val hookResult = injectMember { - method { - name = "test" - returnType = UnitType - } - afterHook { - // ... - } -} -// 在适当的时候调用如下方法即可 -hookResult.remove() -``` - -第二种方法,在 Hook 回调方法中调用 `removeSelf` 移除自身。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "test" - returnType = UnitType - } - afterHook { - // 直接调用如下方法即可 - removeSelf() - } -} -``` - -更多功能请参考 [MemberHookCreator](api/document?id=memberhookcreator-class)。 - -## 异常处理 - -> `YukiHookAPI` 重新设计了对异常的监听,任何异常都不会在 Hook 过程中抛出,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。 - -### 监听异常 - -你可以处理 Hook 方法过程发生的异常。 - -> 示例如下 - -```kotlin -injectMember { - // Your code here. -}.result { - // 处理 Hook 开始时的异常 - onHookingFailure {} - // 处理 Hook 过程中的异常 - onConductFailure { param, throwable -> } - // 处理全部异常 - onAllFailure {} - // ... -} -``` - -在 Resources Hook 时此方法同样适用。 - -> 示例如下 - -```kotlin -injectResource { - // Your code here. -}.result { - // 处理 Hook 时的任意异常 - onHookingFailure {} - // ... -} -``` - -你还可以处理 Hook 的 `Class` 不存在时发生的异常。 - -> 示例如下 - -```kotlin -TargetClass.hook { - injectMember { - // Your code here. - } -}.onHookClassNotFoundFailure { - // Your code here. -} -``` - -你还可以处理查找方法时的异常。 - -> 示例如下 - -```kotlin -method { - // Your code here. -}.onNoSuchMethod { - // Your code here. -} -``` - -更多功能请参考 [MemberHookCreator.Result](api/document?id=result-class) 与 [ResourcesHookCreator.Result](api/document?id=result-class-2)。 - -这里介绍了可能发生的常见异常,若要了解更多请参考 [API 异常处理](config/api-exception.md)。 - -### 抛出异常 - -在某些情况下,你可以**手动抛出异常**来达到提醒某些功能存在问题的目的。 - -上面已经介绍过,在 `hook` 方法体内抛出的异常会被 `YukiHookAPI` 接管,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。 - -以下是 `YukiHookAPI` 接管时这些异常的运作方式。 - -> 示例如下 - -```kotlin -// <情景1> -injectMember { - method { - throw RuntimeException("Exception Test") - } - afterHook { - // ... - } -}.result { - // 能够捕获到 RuntimeException - onHookingFailure {} -} -// <情景2> -injectMember { - method { - // ... - } - afterHook { - throw RuntimeException("Exception Test") - } -}.result { - // 能够捕获到 RuntimeException - onConductFailure { param, throwable -> } -} -``` - -以上情景只会在 (Xposed) 宿主环境被处理,不会对宿主自身造成任何影响。 - -若我们想将这些异常直接抛给宿主,原生的 Xposed 为我们提供了 `param.throwable` 方法,`YukiHookAPI` 同样可以实现此功能。 - -若想在 Hook 回调方法体中将一个异常直接抛给宿主,可以有如下实现方法。 - -> 示例如下 - -```kotlin -injectMember { - method { - // ... - } - afterHook { - RuntimeException("Exception Test").throwToApp() - } -} -``` - -你也可以直接在 Hook 回调方法体中抛出异常,然后标识将异常抛给宿主。 - -> 示例如下 - -```kotlin -injectMember { - method { - // ... - } - afterHook { - throw RuntimeException("Exception Test") - }.onFailureThrowToApp() -} -``` - -以上两种方法均可在宿主接收到异常从而使宿主进程崩溃。 - -!> 为了保证 Hook 调用域与宿主内调用域相互隔离,异常只有在 `beforeHook` 与 `afterHook` 回调方法体中才能抛给宿主。 - -更多功能请参考 [Throwable.throwToApp](api/document?id=throwablethrowtoapp-i-ext-method) 与 [HookCallback](api/document?id=hookcallback-class)。 - -## 状态监听 - -在使用 `XposedHelpers` 的同学往往会在 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. -} -``` - -更多功能请参考 [loadApp](api/document?id=loadapp-method)。 - -### 多个进程 - -如果你 Hook 的宿主 APP 有多个进程,你可以使用 `withProcess` 方法体来对它们分别进行 Hook。 - -> 示例如下 - -```kotlin -withProcess(mainProcessName) { - // Your code here. -} -withProcess(name = "$packageName:tool") { - // Your code here. -} -``` - -更多功能请参考 [withProcess](api/document?id=withprocess-method)。 - -## 写法优化 - -为了使代码更加简洁,你可以删去 `YukiHookAPI` 的名称,将你的 `onHook` 入口写作 `lambda` 形式。 - -> 示例如下 - -```kotlin -override fun onHook() = encase { - // Your code here. -} -``` - -## Xposed 模块判断自身激活状态 - -通常情况下,我们会选择写一个方法,使其返回 `false`,然后 Hook 掉这个方法使其返回 `true` 来证明 Hook 已经生效。 - -在 `YukiHookAPI` 中你完全不需要再这么做了,`YukiHookAPI` 已经帮你封装好了这个操作,你可以直接进行使用。 - -现在,你可以直接使用 `YukiHookAPI.Status.isXposedModuleActive` 在模块中判断自身是否被激活。 - -> 示例如下 - -```kotlin -if(YukiHookAPI.Status.isXposedModuleActive) { - // Your code here. -} -``` - -由于一些特殊原因,在太极、无极中的模块无法使用标准方法检测激活状态。 - -此时你可以使用 `YukiHookAPI.Status.isTaiChiModuleActive` 判断自身是否被激活。 - -> 示例如下 - -```kotlin -if(YukiHookAPI.Status.isTaiChiModuleActive) { - // Your code here. -} -``` - -若你想使用两者得兼的判断方案,`YukiHookAPI` 同样为你封装了便捷的方式。 - -此时你可以使用 `YukiHookAPI.Status.isModuleActive` 判断自身是否在 Xposed 或太极、无极中被激活。 - -> 示例如下 - -```kotlin -if(YukiHookAPI.Status.isModuleActive) { - // Your code here. -} -``` - -更多功能请参考 [YukiHookAPI.Status](api/document?id=status-object)。 - -!> 若模块激活判断中包含太极、无极中的激活状态,就必须将模块的 `Application` 继承于 `ModuleApplication` 或直接使用 `ModuleApplication`。 - -!> `1.0.91` 版本后的 API 修改了激活逻辑判断方式,现在你可以在模块与 Hook APP (宿主) 中同时使用此 API。 - -!> 需要确保 `YukiHookAPI.Configs.isEnableHookModuleStatus` 是启用状态。 - -!> 除了提供标准 API 的 Hook 框架之外,其它情况下模块可能都将无法判断自己是否被激活。 - -

-[浏览下一篇  ➡️](guide/special-feature.md) \ No newline at end of file diff --git a/docs/guide/home.md b/docs/guide/home.md deleted file mode 100644 index 053560b4..00000000 --- a/docs/guide/home.md +++ /dev/null @@ -1,208 +0,0 @@ -# 介绍 - -> 这是一个 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` 语法构建。 - -抛弃原始不太友好的 `XposedHelpers`,你可以使用它来轻松创建 Xposed 模块以及轻松实现自定义 Hook API。 - -## 语言要求 - -请使用 `Kotlin`,框架部分代码构成同样兼容 `Java` 但基础 Hook 场景的实现**可能完全无法使用**。 - -文档全部的 Demo 示例代码都将使用 `Kotlin` 进行描述,如果你完全不会使用 `Kotlin` 那你将有可能无法使用 `YukiHookAPI`。 - -部分 Java Demo 代码可在 [这里](https://github.com/fankes/YukiHookAPI/tree/master/demo-module/src/main/java/com/highcapable/yukihookapi/demo_module/hook/java) 找到,但不推荐使用。 - -## 功能特性 - -- **Xposed 模块开发** - - 自动构建程序可以帮你快速创建一个 Xposed 模块,完全省去配置入口类和 `xposed_init` 文件。 - -- **轻量优雅** - - 拥有一套强大、优雅、人性化、完全使用 `Kotlin` `lambda` 打造的 API,可以帮你快速实现字节码的查找以及方法 Hook。 - -- **高效调试** - - 拥有丰富的调试日志功能,细到每个 Hook 方法的名称、所在类以及查找耗时,可进行快速调试和排错。 - -- **方便移植** - - 原生支持 Xposed API 用法,并原生对接 Xposed API,拥有 Xposed API 的 Hook 框架都能快速对接 `YukiHookAPI`。 - -- **支持混淆** - - 使用 `YukiHookAPI` 构建的 Xposed 模块原生支持 R8 压缩优化混淆,混淆不会破坏 Hook 入口点,R8 下无需任何其它配置。 - -- **快速上手** - - 简单易用,不需要繁琐的配置,不需要十足的开发经验,搭建环境集成依赖即可立即开始使用。 - -## 灵感来源 - -以前,我们在构建 Xposed 模块的时候,首先需要在 `assets` 下创建 `xposed_init` 文件。 - -然后,将自己的入口类名手动填入文件中,使用 `XposedHelpers` 去实现我们的 Hook 逻辑。 - -自 `Kotlin` 作为 Android 主要开发语言以来,这套 API 用起来确实已经不是很优雅了。 - -有没有什么 **好用、轻量、优雅** 的解决办法呢? - -本着这样的想法,`YukiHookAPI` 诞生了。 - -现在,我们只需要编写少量的代码,一切时间开销和花费交给自动化处理。 - -借助 `Kotlin` 优雅的 `lambda` 写法以及 `YukiHookAPI`,可以让你的 Hook 逻辑更加美观清晰。 - -> 示例如下 - - - -#### **Yuki Hook API** - -```kotlin -@InjectYukiHookWithXposed -class HookEntry : IYukiHookXposedInit { - - override fun onHook() = encase { - loadZygote { - ActivityClass.hook { - injectMember { - method { - name = "onCreate" - param(BundleClass) - } - beforeHook { - // Your code here. - } - afterHook { - // Your code here. - } - } - } - resources().hook { - injectResource { - conditions { - name = "sym_def_app_icon" - mipmap() - } - replaceToModuleResource(R.mipmap.ic_launcher) - } - } - } - loadApp(name = "com.android.browser") { - ActivityClass.hook { - injectMember { - method { - name = "onCreate" - param(BundleClass) - } - beforeHook { - // Your code here. - } - afterHook { - // Your code here. - } - } - } - resources().hook { - injectResource { - conditions { - name = "ic_launcher" - mipmap() - } - replaceToModuleResource(R.mipmap.ic_launcher) - } - } - } - } -} -``` - -#### **Xposed API** - -```kotlin -class HookEntry : IXposedHookZygoteInit, IXposedHookLoadPackage, IXposedHookInitPackageResources { - - private lateinit var moduleResources: XModuleResources - - override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam) { - moduleResources = XModuleResources.createInstance(sparam.modulePath, null) - XResources.setSystemWideReplacement("android", "mipmap", "sym_def_app_icon", moduleResources.fwd(R.mipmap.ic_launcher)) - XposedHelpers.findAndHookMethod( - Activity::class.java.name, - null, "onCreate", - Bundle::class.java, - object : XC_MethodHook() { - override fun beforeHookedMethod(param: MethodHookParam?) { - // Your code here. - } - - override fun afterHookedMethod(param: MethodHookParam?) { - // Your code here. - } - }) - } - - 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. - } - }) - } - - override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) { - if (resparam.packageName == "com.android.browser") - resparam.res.setReplacement("com.android.browser", "mipmap", "ic_launcher", moduleResources.fwd(R.mipmap.ic_launcher)) - } -} -``` - - - -是的,你没有看错,仅仅就需要这些代码,就能完全取代 Xposed API 实现同样的功能。 - -现在,借助高效强大的 `YukiHookAPI`,你就可以实现一个非常简单的 Xposed 模块。 - -## 支持的 Hook 框架 - -以下是 `YukiHookAPI` 支持的 `Hook Framework` 以及 Xposed 框架。 - -| Hook Framework | ST | Describe | -| --------------------------------------------------------- | --- | ----------------------------------------------------------------------------------------- | -| [LSPosed](https://github.com/LSPosed/LSPosed) | ✅ | 多场景下稳定使用 | -| [LSPatch](https://github.com/LSPosed/LSPatch) | ⭕ | 将在此项目完善后逐渐加入 API 支持 | -| [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) | ❎ | 未测试,不再推荐使用 | - -

-[浏览下一篇  ➡️](guide/knowledge.md) \ No newline at end of file diff --git a/docs/guide/knowledge.md b/docs/guide/knowledge.md deleted file mode 100644 index 72088ad8..00000000 --- a/docs/guide/knowledge.md +++ /dev/null @@ -1,88 +0,0 @@ -# 基础知识 - -> 这里收集了 Xposed 相关的介绍以及开启前需要掌握的知识要点,已经了解的同学可以略过。 - -基础知识内容**并不一定完全准确**,请根据自己的见解酌情阅读,若发现内容**有错误欢迎指正并帮助我们完善和改进**。 - -## 相关介绍 - -> 这里介绍了 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 出现到现在为止,除了开发者人人皆知的 `XposedHelpers`,依然没有一套针对 `Kotlin` 打造的语法糖以及用法封装十分完善的 API。 - -本 API 框架的诞生就是希望在 Xposed 的如今时代,能让更多有动手能力的 Xposed 模块开发者少走弯路,更容易、更简单地完成整个开发流程。 - -未来,`YukiHookAPI` 将在使用 Xposed API 的目标基础上适配更多第三方 Hook 框架,使得整个生态得到完善,并帮助更多开发者让 Xposed 模块开发变得更加简单和易懂。 - -## 让我们开始吧 - -在开始之前,你需要拥有以下基础才能更好地使用 `YukiHookAPI`。 - -- 掌握并了解 Android 开发及简单的系统运行原理 - -- 掌握并了解 Android APK 内部结构以及简单的反编译知识要领,可参考 [Jadx](https://github.com/skylot/jadx) 与 [ApkTool](https://github.com/iBotPeaches/Apktool) - -- 掌握并熟练使用 Java 反射,了解简单的 Smali 语法,了解 DEX 文件结构,会使用逆向分析定位方法位置 - -- 掌握基础的原生 [Xposed API](https://api.xposed.info) 用法,了解 Xposed 的运行原理,可参考本文以及 [这里](https://blog.ketal.icu/2022/01/13/Xposed%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%95%99%E7%A8%8B) **(友情链接)** - -- 掌握 Kotlin 语言,学会灵活运用 [Kotlin lambda](https://blog.ketal.icu/2022/01/01/kotlin-lambda%E5%85%A5%E9%97%A8) **(友情链接)** - -- 掌握并了解 Kotlin 与 Java 混编、互相调用以及 Kotlin 生成的 Java 字节码 - -

-[浏览下一篇  ➡️](guide/quick-start.md) \ No newline at end of file diff --git a/docs/guide/move-to-new-api.md b/docs/guide/move-to-new-api.md deleted file mode 100644 index 5b9146d3..00000000 --- a/docs/guide/move-to-new-api.md +++ /dev/null @@ -1,257 +0,0 @@ -# 从 Xposed API 迁移 - -> 若你熟悉 Xposed API,你可以参考下方的相同点将自己的 API 快速迁移到 `YukiHookAPI`。 - -## 迁移 Hook 入口点 - -> 从 `XC_LoadPackage.LoadPackageParam` 迁移至 `PackageParam`。 - -`YukiHookAPI` 对 `PackageParam` 实现了 `lambda` 方法体 `this` 用法,在 `encase` 方法体内即可全局得到 `PackageParam` 对象。 - -> API 功能差异对比如下 - - - -#### **Yuki Hook API** - -```kotlin -override fun onHook() = encase { - // 得到当前 Hook 的包名 - packageName - // 得到当前 Hook 的 ApplicationInfo - appInfo - // 得到宿主 Application 生命周期 - appContext - // Hook 指定的 APP - loadApp(name = "com.demo.test") { - // Class Hook - findClass("com.demo.test.TestClass").hook { - injectMember { - method { - name = "test" - param(BooleanType) - } - afterHook { - // ... - } - } - } - // Resources Hook (固定用法) - resources().hook { - injectResource { - conditions { - name = "ic_launcher" - mipmap() - } - replaceToModuleResource(R.mipmap.ic_launcher) - } - } - } -} -``` - -#### **Xposed API** - -```kotlin -private lateinit var moduleResources: XModuleResources - -override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam) { - moduleResources = XModuleResources.createInstance(sparam.modulePath, null) -} - -override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - // 得到当前 Hook 的包名 - lpparam.packageName - // 得到当前 Hook 的 ApplicationInfo - lpparam.applicationInfo - // 得到宿主 Application 生命周期 - AndroidAppHelper.currentApplication() - // Class Hook - if(lpparam.packageName == "com.demo.test") - XposedHelpers.findAndHookMethod("com.demo.test.TestClass", lpparam.classLoader, "test", Boolean::class.java, object : XC_MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - // ... - } - }) -} - -override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) { - // 得到当前 Hook 的包名 - resparam.packageName - // 创建 Hook - resparam.res.setReplacement("com.demo.test", "mipmap", "ic_launcher", moduleResources.fwd(R.mipmap.ic_launcher)) -} -``` - - - -## 迁移 Hook 方法体 - -> 从 `XC_MethodHook.MethodHookParam` 迁移至 `HookParam`。 - -### Before/After Hook - -`YukiHookAPI` 同样对 `HookParam` 实现了 `lambda` 方法体 `this` 用法,在 `beforeHook`、`afterHook` 等方法体内即可全局得到 `HookParam` 对象。 - -> API 功能差异对比如下 - - - -#### **Yuki Hook API** - -```kotlin -afterHook { - // 得到当前 Hook 的实例 - instance - // 得到当前 Hook 的 Class 实例 - instanceClass - // 得到并 cast 当前 Hook 的实例为指定类型 T - instance() - // 得到方法参数数组 - args - // 得到方法参数的第一位 T - args().first().cast() - // 得到方法参数的最后一位 T - args().last().cast() - // 得到方法参数的任意下标 T,这里用 2 举例 - args(index = 2).cast() - // 设置方法参数的任意下标,这里用 2 举例 - args(index = 2).set(...) - // 得到返回值 - result - // 得到返回值并 cast 为 T - result() - // 修改返回值内容 - result = ... - // 删除返回值内容 - resultNull() - // 向 Hook APP 抛出异常 - Throwable("Fatal").throwToApp() - // 执行未经 Hook 的原始方法并使用原始方法参数调用,泛型可略 - callOriginal() - // 执行未经 Hook 的原始方法并自定义方法参数调用,泛型可略 - invokeOriginal(...) -} -``` - -#### **Xposed API** - -```kotlin -override fun afterHookedMethod(param: MethodHookParam) { - // 得到当前 Hook 的实例 - param.thisObject - // 得到当前 Hook 的 Class 实例 - param.thisObject.javaClass - // 得到并 cast 当前 Hook 的实例为指定类型 T - param.thisObject as T - // 得到方法参数数组 - param.args - // 得到方法参数的第一位 T - param.args[0] as T - // 得到方法参数的最后一位 T - param.args[param.args.lastIndex] as T - // 得到方法参数的任意下标 T,这里用 2 举例 - param.args[2] as T - // 设置方法参数的任意下标,这里用 2 举例 - param.args[2] = ... - // 得到返回值 - param.result - // 得到返回值并 cast 为 T - param.result as T - // 修改返回值内容 - param.result = ... - // 删除返回值内容 - param.result = null - // 向 Hook APP 抛出异常 - param.throwable = Throwable("Fatal") - // 执行未经 Hook 的原始方法 - XposedBridge.invokeOriginalMethod(param.method, param.thisObject, ...) -} -``` - - - -### Replace Hook - -`replaceHook` 方法比较特殊,`YukiHookAPI` 为它做出了多种形式以供选择。 - -> API 功能差异对比如下 - - - -#### **Yuki Hook API** - -无返回值的方法 `void`。 - -```kotlin -replaceUnit { - // 直接在这里实现被替换的逻辑 -} -``` - -有返回值的方法。 - -```kotlin -replaceAny { - // 在这里实现被替换的逻辑 - // ... - // 需要返回方法对应的返回值,无需写 return,只需将参数放到最后一位 - // 假设这个方法的返回值是 Int,我们只需要保证最后一位是我们需要的返回值即可 - 0 -} -``` - -有些方法我们只需替换其返回值,则有如下实现。 - -```kotlin -// 替换为你需要的返回值 -replaceTo(...) -// 替换为 Boolean 类型的返回值 -replaceToTrue() -// 拦截返回值 -intercept() -``` - -!> 直接替换返回值的方法传入的参数是固定不变的,若想实现动态替换返回值请使用 `replaceAny` 方法体。 - -#### **Xposed API** - -无返回值的方法 `void`。 - -```kotlin -override fun replaceHookedMethod(param: MethodHookParam): Any? { - // 直接在这里实现被替换的逻辑 - return null -} -``` - -有返回值的方法。 - -```kotlin -override fun replaceHookedMethod(param: MethodHookParam): Int { - // 在这里实现被替换的逻辑 - // ... - // 假设这个方法的返回值是 Int - return 0 -} -``` - -有些方法我们只需替换其返回值,则有如下实现。 - -```kotlin -// 替换为你需要的返回值 -override fun replaceHookedMethod(param: MethodHookParam) = ... -// 替换为 Boolean 类型的返回值 -override fun replaceHookedMethod(param: MethodHookParam) = true -// 拦截返回值 -override fun replaceHookedMethod(param: MethodHookParam) = null -``` - - - -## 迁移其它功能 - -`YukiHookAPI` 对 Xposed API 进行了完全重写,你可以参考 [API 文档](api/home) 以及 [特色功能](guide/special-feature) 来决定一些功能性的迁移和使用。 - -

-[浏览下一篇  ➡️](config/api-example.md) \ No newline at end of file diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md deleted file mode 100644 index 61ce553d..00000000 --- a/docs/guide/quick-start.md +++ /dev/null @@ -1,187 +0,0 @@ -# 快速开始 - -> 集成 `YukiHookAPI` 到你的项目中。 - -## 环境要求 - -- Windows 7 及以上/macOS 10.14 及以上/Linux 发行版(Arch/Debian) - -- Android Studio 2021.1 及以上 - -- IntelliJ IDEA 2021.1 及以上 - -- Kotlin 1.7.0 及以上 - -- Android Gradle Plugin 7.0 及以上 - -- Gradle 7.0 及以上 - -- Jvm 11 及以上 (Since API `1.0.80`) - -## 自动构建项目 - -`YukiHookAPI` 提供了一个自动化构建工具,它可以帮助你快速构建一个拥有 Xposed 模块依赖的 Android 标准项目模板,使用构建好的模板即可直接开始下一步工作。 - -你可以 [点击这里](tools/yukihookapi-projectbuilder) 进行查看。 - -## 手动配置项目 - -若你不想使用自动化构建工具,你依然可以按照以下方式手动配置项目依赖。 - -### 集成依赖 - -在你的项目 `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` 依赖的版本必须一一对应,否则将会造成版本不匹配错误。 - -在你的 app `build.gradle` 中修改 `Kotlin` 的 Jvm 版本为 11 及以上。 - -> 示例如下 - -```gradle -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_11 - targetCompatibility JavaVersion.VERSION_11 - } - kotlinOptions { - jvmTarget = '11' - } -} -``` - -!> 自 API `1.0.80` 版本后 Jvm 版本默认为 11,不再支持 1.8 及以下版本。 - -### 作为 Xposed 模块使用 - -在你的 `AndroidManifest.xml` 中添加基础代码。 - -> 示例如下 - -```xml - - - - - - - - - - - -``` - -在你的项目中创建一个 Hook 入口类,继承于 `IYukiHookXposedInit` 并加入注解 `@InjectYukiHookWithXposed`。 - -> 示例如下 - -```kotlin -@InjectYukiHookWithXposed -class HookEntry : IYukiHookXposedInit { - - override fun onHook() = YukiHookAPI.encase { - // Your code here. - } -} -``` - -**(建议)** 你可以将你的模块 APP 的 `Application` 继承于 `ModuleApplication` 以实现完整使用体验。 - -详情请参考 [ModuleApplication](api/document?id=moduleapplication-class)。 - -然后,你就可以开始编写 Hook 代码了。 - -有关作为 Xposed 模块使用的相关配置详细内容,你可以 [点击这里](config/xposed-using) 继续阅读。 - -若你目前正在使用 Xposed API,你可以参考 [从 Xposed API 迁移](guide/move-to-new-api)。 - -### 作为 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`~~ ~~`YukiHookDataChannel`~~ 以及 Resources Hook 功能将失效。 - -

-[浏览下一篇  ➡️](guide/example.md) \ No newline at end of file diff --git a/docs/guide/special-feature.md b/docs/guide/special-feature.md deleted file mode 100644 index ee8321e0..00000000 --- a/docs/guide/special-feature.md +++ /dev/null @@ -1,1866 +0,0 @@ -# 特色功能 - -> 除了基本的 Hook 功能之外,`YukiHookAPI` 还为开发者提供了大量的语法糖和扩展用法。 - -## 字节码扩展功能 - -假设有一个这样的 `Class`。 - -> 示例如下 - -```java -package com.demo; - -public class BaseTest { - - public BaseTest() { - // ... - } - - public BaseTest(boolean isInit) { - // ... - } - - private void doBaseTask(String taskName) { - // ... - } -} -``` - -```java -package com.demo; - -public class Test extends BaseTest { - - public Test() { - // ... - } - - public Test(boolean isInit) { - // ... - } - - private static TAG = "Test"; - - private BaseTest baseInstance; - - 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) { - // ... - } -} -``` - -### 查询与反射调用 - -假设我们要得到 `Test`(以下统称“当前 `Class`”)的 `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).any() // any 为 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" - emptyParam() - returnType = StringType -}.get(instance).string() // 得到方法的结果 -``` - -通过观察发现,这个 `Class` 中只有一个名为 `getName` 的方法,那我们可不可以再简单一点呢? - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name = "getName" - emptyParam() -}.get(instance).string() // 得到方法的结果 -``` - -是的,对于确切不会变化的方法,你可以精简查询条件。 - -在只使用 `get` 或 `wait` 方法得到结果时 `YukiHookAPI` **会默认按照字节码顺序匹配第一个查询到的结果**。 - -问题又来了,这个 `Class` 中有一个 `release` 方法,但是它的方法参数好长,而且很多的类型都无法直接得到。 - -通常情况下我们会使用 `param(...)` 来查询这个方法,但是有没有更简单的方法呢。 - -此时,在确定方法唯一性后,你可以使用 `paramCount` 来查询到这个方法。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name = "release" - // 此时我们不必确定方法参数具体类型,写个数就好 - paramCount = 3 -}.get(instance) // 得到这个方法 -``` - -### 在父类查询 - -你会注意到 `Test` 继承于 `BaseTest`,现在我们想得到 `BaseTest` 的 `doBaseTask` 方法,在不知道父类名称的情况下,要怎么做呢? - -参照上面的查询条件,我们只需要在查询条件中加入一个 `superClass` 即可实现这个功能。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name = "doBaseTask" - param(StringType) - // 只需要添加这个条件 - superClass() -}.get(instance).call("task_name") -``` - -这个时候我们就可以在父类中取到这个方法了。 - -`superClass` 有一个参数为 `isOnlySuperClass`,设置为 `true` 后,可以跳过当前 `Class` 仅查询当前 `Class` 的父类。 - -由于我们现在已知 `doBaseTask` 方法只存在于父类,可以加上这个条件节省查询时间。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name = "doBaseTask" - param(StringType) - // 加入一个查询条件 - superClass(isOnlySuperClass = true) -}.get(instance).call("task_name") -``` - -这个时候我们同样可以得到父类中的这个方法。 - -`superClass` 一旦设置就会自动循环向后查找全部继承的父类中是否有这个方法,直到查询到目标没有父类(继承关系为 `java.lang.Object`)为止。 - -更多用法可参考 [superClass 方法](api/document?id=superclass-method)。 - -!> 当前查询的 `Method` 除非指定 `superClass` 条件,否则只能查询到当前 `Class` 的 `Method`。 - -### 模糊查询 - -如果我们想查询一个方法名称,但是又不确定它在每个版本中是否发生变化,此时我们就可以使用模糊查询功能。 - -假设我们要得到 `Class` 中的 `doTask` 方法,可以使用如下实现。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name { - // 设置名称不区分大小写 - equalsOf(other = "dotask", isIgnoreCase = true) - } - param(StringType) -}.get(instance).call("task_name") -``` - -已知当前 `Class` 中仅有一个 `doTask` 方法,我们还可以判断方法名称仅包含其中指定的字符。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name { - // 仅包含 oTas - contains(other = "oTas") - } - param(StringType) -}.get(instance).call("task_name") -``` - -我们还可以根据首尾字符串进行判断。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name { - // 开头包含 do - startsWith(prefix = "do") - // 结尾包含 Task - endsWith(suffix = "Task") - } - param(StringType) -}.get(instance).call("task_name") -``` - -通过观察发现这个方法名称中只包含字母,我们还可以再增加一个精确的查询条件。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name { - // 开头包含 do - startsWith(prefix = "do") - // 结尾包含 Task - endsWith(suffix = "Task") - // 仅包含字母 - onlyLetters() - } - param(StringType) -}.get(instance).call("task_name") -``` - -更多用法可参考 [NameConditions](api/document?id=nameconditions-class)。 - -### 多重查询 - -有些时候,我们可能需要查询一个 `Class` 中具有相同特征的一组方法、构造方法、变量,这个时候,我们就可以利用相对条件匹配来完成。 - -在查询条件结果的基础上,我们只需要把 `get` 换为 `all` 即可得到匹配条件的全部字节码。 - -假设这次我们要得到 `Class` 中方法参数个数范围在 `1..3` 的全部方法,可以使用如下实现。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - paramCount(1..3) -}.all(instance).forEach { instance -> - // 调用执行每个方法 - instance.call(...) -} -``` - -上述示例可完美匹配到如下 3 个方法。 - -`private void doTask(String taskName)` - -`private void release(Release release, Function function, Task task)` - -`private void b(String a)` - -通过观察 `Class` 中有两个名称为 `b` 的方法,可以使用如下实现。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name = "b" -}.all(instance).forEach { instance -> - // 调用执行每个方法 - instance.call(...) -} -``` - -上述示例可完美匹配到如下 2 个方法。 - -`private void b()` - -`private void b(String a)` - -### 静态字节码 - -有些方法和变量在 `Class` 中是静态的实现,这个时候,我们不需要传入实例就可以调用它们。 - -假设我们这次要得到静态变量 `TAG` 的内容。 - -> 示例如下 - -```kotlin -Test::class.java.field { - name = "TAG" - type = StringType -}.get().string() // Field 的类型是字符串,可直接进行 cast -``` - -假设 `Class` 中存在同名的非静态 `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" - emptyParam() -}.get().call() -``` - -同样地,你可以标识它是一个静态。 - -> 示例如下 - -```kotlin -Test::class.java.method { - name = "init" - emptyParam() - modifiers { - // 标识查询的这个方法需要是静态 - asStatic() - } -}.get().call() -``` - -### 混淆的字节码 - -你可能已经注意到了,这里给出的示例 `Class` 中有两个混淆的变量名称,它们都是 `a`,这个时候我们要怎么得到它们呢? - -有两种方案。 - -第一种方案,确定变量的名称和类型。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.field { - name = "a" - type = BooleanType -}.get(instance).any() // 得到名称为 a 类型为 Boolean 的变量 -``` - -第二种方案,确定变量的类型所在的位置。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.field { - type(BooleanType).index().first() -}.get(instance).any() // 得到第一个类型为 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" - emptyParam() - }.call() - // 得到 name - val name = method { name = "getName" }.string() -} -``` - -我们还可以用 `superClass` 调用当前 `Class` 父类的方法。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 假设这个 Class 是不能被直接得到的 -instance.current { - // 执行父类的 doBaseTask 方法 - superClass().method { - name = "doBaseTask" - param(StringType) - }.call("task_name") -} -``` - -如果你不喜欢使用一个大括号的调用域来创建当前实例的命名空间,你可以直接使用 `current()` 方法。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例,这个 Class 是不能被直接得到的 -val instance = Test() -// 执行 doTask 方法 -instance - .current() - .method { - name = "doTask" - param(StringType) - }.call("task_name") -// 执行 stop 方法 -instance - .current() - .method { - name = "stop" - emptyParam() - }.call() -// 得到 name -val name = instance.current().method { name = "getName" }.string() -``` - -同样地,它们之间可以连续调用,但**不允许内联调用**。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 假设这个 Class 是不能被直接得到的 -instance.current { - method { - name = "doTask" - param(StringType) - }.call("task_name") -}.current() - .method { - name = "stop" - emptyParam() - }.call() -// ❗注意,因为 current() 返回的是 CurrentClass 自身对象,所以不能像下面这样调用 -instance.current().current() -``` - -针对 `Field` 实例,还有一个便捷的方法,可以直接获取 `Field` 所在实例的对象。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 假设这个 Class 是不能被直接得到的 -instance.current { - // <方案1> - field { - name = "baseInstance" - }.current { - method { - name = "doBaseTask" - param(StringType) - }.call("task_name") - } - // <方案2> - field { - name = "baseInstance" - }.current() - ?.method { - name = "doBaseTask" - param(StringType) - }?.call("task_name") -} -``` - -上述 `current` 方法相当于帮你调用了 `CurrentClass` 中的 `field { ... }.any()?.current()` 方法。 - -!> 若不存在 `CurrentClass` 调用域,你需要使用 `field { ... }.get(instance).current()` 来进行调用。 - -问题又来了,我想使用反射的方式创建如下的实例并调用其中的方法,该怎么做呢? - -> 示例如下 - -```kotlin -Test(true).doTask("task_name") -``` - -通常情况下,我们可以使用标准的反射 API 来调用。 - -> 示例如下 - -```kotlin -"com.demo.Test".toClass() - .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 -"com.demo.Test".toClass().buildOfAny(true) { param(BooleanType) }?.current { - method { - name = "doTask" - param(StringType) - }.call("task_name") -} -``` - -更多用法可参考 [CurrentClass](api/document?id=currentclass-class) 以及 [Class.buildOf](api/document?id=classbuildof-ext-method) 方法。 - -### 原始调用 - -若你正在使用反射调用的一个方法是被 Hook 过的,此时我们如何调用其原始方法呢? - -`XposedBridge` 为我们提供了一个 `XposedBridge.invokeOriginalMethod` 功能,现在,在 `YukiHookAPI` 中你可以便捷地实现这个功能。 - -假设下面是我们要演示的 `Class`。 - -> 示例如下 - -```java -public class Test { - - public static String getString() { - return "Original"; - } -} -``` - -下面是 Hook 这个 `Class` 中 `getString` 方法的方式。 - -> 示例如下 - -```kotlin -Test::class.java.hook { - injectMember { - method { - name = "getString" - emptyParam() - returnType = StringType - } - replaceTo("Hooked") - } -} -``` - -此时,我们再使用反射调用这个方法,则会得到 Hook 后的结果 `"Hooked"`。 - -> 示例如下 - -```kotlin -// result 的结果会是 "Hooked" -val result = Test::class.java.method { - name = "getString" - emptyParam() - returnType = StringType -}.get().string() -``` - -如果我们想得到这个方法未经 Hook 的原始方法及结果,只需要在结果中加入 `original` 即可。 - -> 示例如下 - -```kotlin -// result 的结果会是 "Original" -val result = Test::class.java.method { - name = "getString" - emptyParam() - returnType = StringType -}.get().original().string() -``` - -更多用法可参考 [MethodFinder.Result.original](api/document?id=original-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" - emptyParam() -}.remedys { - method { - name = "doTask" - param(StringType) - }.onFind { - // 可在这里实现找到的逻辑 - } - method { - name = "doTask" - param(StringType, IntType) - }.onFind { - // 可在这里实现找到的逻辑 - } -}.wait(instance) { - // 得到方法的结果 -} -``` - -!> 特别注意使用了 `RemedyPlan` 的方法查询结果不能再使用 `get` 的方式得到方法实例,应当使用 `wait` 方法。 - -另外,你还可以在使用 [多重查询](guide/special-feature?id=多重查询) 的情况下继续使用 `RemedyPlan`。 - -> 示例如下 - -```kotlin -// 假设这就是这个 Class 的实例 -val instance = Test() -// 使用 YukiHookAPI 调用并执行 -Test::class.java.method { - name = "doTask" - emptyParam() -}.remedys { - method { - name = "doTask" - paramCount(0..1) - }.onFind { - // 可在这里实现找到的逻辑 - } - method { - name = "doTask" - paramCount(1..2) - }.onFind { - // 可在这里实现找到的逻辑 - } -}.waitAll(instance) { - // 得到方法的结果 -} -``` - -以当前 `Class` 举例,若 [多重查询](guide/special-feature?id=多重查询) 结合 `RemedyPlan` 在创建 Hook 的时候使用,你需要稍微改变一下用法。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "doTask" - emptyParam() - }.remedys { - method { - name = "doTask" - paramCount(0..1) - } - method { - name = "doTask" - paramCount(1..2) - } - }.all() - beforeHook {} - afterHook {} -} -``` - -在创建 Hook 的时候使用可参考 [MethodFinder.Process.all](api/document?id=all-method-1)、[ConstructorFinder.Process.all](api/document?id=all-method-3)。 - -更多用法可参考 [MethodFinder.RemedyPlan](api/document?id=remedyplan-class)、[ConstructorFinder.RemedyPlan](api/document?id=remedyplan-class-1)、[FieldFinder.RemedyPlan](api/document?id=remedyplan-class-2)。 - -### 相对匹配 - -假设宿主中不同版本中存在功能相同的 `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()) "com.demo.ATest".toClass() else "com.demo.BTest".toClass() -// 然后再查询这个方法并调用 -currentClass.method { - name = "doTask" - emptyParam() -}.get().call() -``` - -感觉这种方案非常的不优雅且繁琐,那么此时 `YukiHookAPI` 就为你提供了一个非常方便的 `VariousClass` 专门来解决这个问题。 - -现在,你可以直接使用以下方式获取到这个 `Class`。 - -> 示例如下 - -```kotlin -VariousClass("com.demo.ATest", "com.demo.BTest").get().method { - name = "doTask" - emptyParam() -}.get().call() -``` - -若当前 `Class` 在指定的 `ClassLoader` 中存在,你可以在 `get` 中填入你的 `ClassLoader`。 - -> 示例如下 - -```kotlin -val customClassLoader: ClassLoader? = ... // 假设这个就是你的 ClassLoader -VariousClass("com.demo.ATest", "com.demo.BTest").get(customClassLoader).method { - name = "doTask" - emptyParam() -}.get().call() -``` - -若你正在 `PackageParam` 中操作 (Xposed) 宿主环境的 `Class`,可以直接使用 `clazz` 进行设置。 - -> 示例如下 - -```kotlin -VariousClass("com.demo.ATest", "com.demo.BTest").clazz.method { - name = "doTask" - emptyParam() -}.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) -} -``` - -#### 必要的查询条件 - -!> 在普通方法查询条件中,**即使是无参的方法也需要设置查询条件**。 - -假设我们有如下的 `Class`。 - -> 示例如下 - -```java -public class TestFoo { - - public void foo(String string) { - // ... - } - - public void foo() { - // ... - } -} -``` - -我们要得到其中的 `public void foo()` 方法,可以写作如下形式。 - -> 示例如下 - -```kotlin -TestFoo::class.java.method { - name = "foo" -} -``` - -但是,上面的例子**是错误的**。 - -你会发现这个 `Class` 中有两个 `foo` 方法,其中一个带有方法参数。 - -由于上述例子没有设置 `param` 的查询条件,得到的结果将会是匹配名称且匹配字节码顺序的第一个方法 `public void foo(String string)`,而不是我们需要的最后一个方法。 - -这是一个**经常会出现的错误**,**没有方法参数就会丢失方法参数查询条件**的使用问题。 - -正确的使用方法如下。 - -> 示例如下 - -```kotlin -TestFoo::class.java.method { - name = "foo" - // ✅ 正确的使用方法,添加详细的筛选条件 - emptyParam() -} -``` - -至此,上述的示例将可以完美地匹配到 `public void foo()` 方法。 - -> PS:在较旧的 API 版本中是允许匹配不写默认匹配无参方法的做法的,但是最新版本更正了这一问题,请确保你使用的是最新的 API 版本。 - -#### 可简写查询条件 - -> 在构造方法查询条件中,**无参的构造方法可以不需要填写查询条件**。 - -假设我们有如下的 `Class`。 - -> 示例如下 - -```java -public class TestFoo { - - public TestFoo() { - // ... - } -} -``` - -我们要得到其中的 `public TestFoo()` 构造方法,可以写作如下形式。 - -> 示例如下 - -```kotlin -TestFoo::class.java.constructor { emptyParam() } -``` - -上面的例子可以成功获取到 `public TestFoo()` 构造方法,但是感觉有一些繁琐。 - -与普通方法不同,由于构造方法不需要考虑 `name` 名称,当构造方法没有参数的时候,我们可以省略 `emptyParam` 参数。 - -> 示例如下 - -```kotlin -TestFoo::class.java.constructor() -``` - -!> PS:在旧的 API 版本中构造方法不填写任何查询参数会直接找不到构造方法,**这是一个 BUG,最新版本已经进行修复**,请确保你使用的是最新的 API 版本。 - -#### 字节码类型 - -!> 在字节码调用结果中,`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=componenttypefactory-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 -``` - -你还可以使用 `LoggerType` 自定义日志打印的类型,可选择使用 `android.util.Log` 还是 `XposedBridge.log` 来打印日志。 - -默认类型为 `LoggerType.BOTH`,含义为同时使用这两个方法来打印日志。 - -比如我们仅使用 `android.util.Log` 来打印日志。 - -> 示例如下 - -```kotlin -loggerD(tag = "YukiHookAPI", msg = "This is a log", type = LoggerType.LOGD) -``` - -或又仅使用 `XposedBridge.log` 来打印日志,此方法仅可在 (Xposed) 宿主环境使用。 - -> 示例如下 - -```kotlin -loggerD(tag = "YukiHookAPI", msg = "This is a log", type = LoggerType.XPOSEDBRIDGE) -``` - -若你想智能区分 (Xposed) 宿主环境与模块环境,可以写为如下形式。 - -> 示例如下 - -```kotlin -loggerD(tag = "YukiHookAPI", msg = "This is a log", type = LoggerType.SCOPE) -``` - -这样 API 就会在不同环境智能选择指定的方法类型去打印这条日志。 - -更多用法可参考 [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 throwable = Throwable(...) -// 打印日志 -loggerE(msg = "This is an error", e = throwable) -``` - -打印的结果为如下所示。 - -> 示例如下 - -``` -[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 -``` - -在错误日志中,你同样也可以使用 `LoggerType` 来指定当前打印日志所用到的方法类型。 - -更多用法可参考 [loggerE](api/document?id=loggere-method) 方法。 - -## Xposed 模块数据存储功能 - -> 这是一个自动对接 `SharedPreferences` 和 `XSharedPreferences` 的高效模块数据存储解决方案。 - -我们需要存储模块的数据,以供宿主调用,这个时候会遇到原生 `Sp` 存储的数据互通阻碍。 - -原生的 `Xposed` 给我们提供了一个 `XSharedPreferences` 用于读取模块的 `Sp` 数据。 - -### 在 Activity 中使用 - -> 这里描述了在 `Activity` 中装载 `YukiHookModulePrefs` 的场景。 - -通常情况下我们可以这样在 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)。 - -### 在 PreferenceFragment 中使用 - -> 这里描述了在 `PreferenceFragment` 中装载 `YukiHookModulePrefs` 的场景。 - -若你的模块使用了 `PreferenceFragmentCompat`,你现在可以将其继承类开始迁移到 `ModulePreferenceFragment`。 - -!> 你必须继承 `ModulePreferenceFragment` 才能实现 `YukiHookModulePrefs` 的模块存储功能。 - -详情请参考 [ModulePreferenceFragment](api/document?id=modulepreferencefragment-class)。 - -## Xposed 模块与宿主通讯桥功能 - -> 这是一个使用系统无序广播在模块与宿主之间发送和接收数据的解决方案。 - -!> 需要满足的条件:模块与宿主需要保持存活状态,否则无法建立通讯。 - -### 基本用法 - -> 这里描述了 `wait` 与 `put` 方法的基本使用方法。 - -通过使用 `dataChannel` 来实现模块与宿主之间的通讯桥,原理为发送接收系统无序广播。 - -> 模块示例如下 - -```kotlin -// 从指定包名的宿主获取 -dataChannel(packageName = "com.example.demo").wait(key = "key_from_host") { value -> - // Your code here. -} -// 发送给指定包名的宿主 -dataChannel(packageName = "com.example.demo").put(key = "key_from_module", value = "I am module") -``` - -> 宿主示例如下 - -```kotlin -// 从模块获取 -dataChannel.wait(key = "key_from_module") { value -> - // Your code here. -} -// 发送给模块 -dataChannel.put(key = "key_from_host", value = "I am host") -``` - -你可以不设置 `dataChannel` 的 `value` 来达到仅通知模块或宿主回调 `wait` 方法。 - -> 模块示例如下 - -```kotlin -// 从指定包名的宿主获取 -dataChannel(packageName = "com.example.demo").wait(key = "listener_from_host") { - // Your code here. -} -// 发送给指定包名的宿主 -dataChannel(packageName = "com.example.demo").put(key = "listener_from_module") -``` - -> 宿主示例如下 - -```kotlin -// 从模块获取 -dataChannel.wait(key = "listener_from_module") { - // Your code here. -} -// 发送给模块 -dataChannel.put(key = "listener_from_host") -``` - -!> 接收方需要保持存活状态才能收到通讯数据。 - -详情请参考 [YukiHookDataChannel](api/document?id=yukihookdatachannel-class)。 - -### 判断模块与宿主版本是否匹配 - -> 通过通讯桥功能,`YukiHookAPI` 还为你提供了在用户更新模块后,判断模块是否与宿主版本匹配的解决方案。 - -我们只需要调用 `checkingVersionEquals` 方法,即可实现这个功能。 - -在模块与宿主中可进行双向判断。 - -你可以在模块中判断指定包名的宿主是否与当前模块的版本匹配。 - -> 示例如下 - -```kotlin -// 从指定包名的宿主获取 -dataChannel(packageName = "com.example.demo").checkingVersionEquals { isEquals -> - // Your code here. -} -``` - -你还可以在宿主中判断是否自身与当前模块的版本匹配。 - -> 示例如下 - -```kotlin -// 从模块获取 -dataChannel.checkingVersionEquals { isEquals -> - // Your code here. -} -``` - -!> 方法回调的条件为宿主、模块保持存活状态,并在激活模块后重启了作用域中的 Hook 目标宿主对象。 - -详情请参考 [YukiHookDataChannel](api/document?id=yukihookdatachannel-class)。 - -### 回调事件响应的规则 - -!> 在模块和宿主中,每一个 `dataChannel` 对应的 `key` 的回调事件**都不允许重复创建**,若重复,之前的回调事件会被新增加的回调事件替换,若在模块中使用,在同一个 `Activity` 中不可以重复,不同的 `Activity` 中相同的 `key` 允许重复。 - -这里只列出了在模块中使用的例子,在宿主中相同的 `key` 始终不允许重复创建。 - -> 示例如下 - -```kotlin -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // 回调事件 A - dataChannel(packageName = "com.example.demo").wait(key = "test_key") { - // Your code here. - } - // 回调事件 B - dataChannel(packageName = "com.example.demo").wait(key = "test_key") { - // Your code here. - } - // 回调事件 C - dataChannel(packageName = "com.example.demo").wait(key = "other_test_key") { - // Your code here. - } - } -} - -class OtherActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // 回调事件 D - dataChannel(packageName = "com.example.demo").wait(key = "test_key") { - // Your code here. - } - } -} -``` - -在上述示例中,回调事件 A 会被回调事件 B 替换掉,回调事件 C 的 `key` 不与其它重复,回调事件 D 在另一个 Activity 中,所以最终回调事件 B、C、D 都可被创建成功。 - -!> 一个相同 `key` 的回调事件只会回调当前模块正在显示的 `Activity` 中注册的回调事件,例如上述中的 `test_key`,如果 `OtherActivity` 正在显示,那么 `MainActivity` 中的 `test_key` 就不会被回调。 - -!> 请特别注意,相同的 `key` 在同一个 `Activity` 不同的 `Fragment` 中注册 `dataChannel`,它们依然会在当前 `Activity` 中同时被回调。 - -!> 在模块中,你只能使用 `Activity` 的 `Context` 注册 `dataChannel`,你不能在 `Application` 以及 `Service` 等地方使用 `dataChannel`,若要在 `Fragment` 中使用 `dataChannel`,请使用 `activity?.dataChannel(...)`。 - -### 安全性说明 - -!> 在模块环境中,你只能接收**指定包名的宿主**发送的通讯数据且只能发送给**指定包名的宿主**。 - -为了进一步防止广播滥用,通讯数据中 API 会自动指定宿主和模块的包名,防止其它 APP 监听并利用广播做出超限行为。 - -## 宿主生命周期扩展功能 - -> 这是一个自动 Hook 宿主 APP 生命周期的扩展功能。 - -### 监听生命周期 - -> 通过自动化 Hook 宿主 APP 的生命周期方法,来实现监听功能。 - -我们需要监听宿主 `Application` 的启动和生命周期方法,只需要使用以下方式实现。 - -> 示例如下 - -```kotlin -loadApp(name = "com.example.demo") { - // 注册生命周期监听 - onAppLifecycle { - // 你可以在这里实现 Application 中的生命周期方法监听 - attachBaseContext { baseContext, hasCalledSuper -> - // 通过判断 hasCalledSuper 来确定是否已执行 super.attachBaseContext(base) 方法 - // ... - } - onCreate { - // 通过 this 得到当前 Application 实例 - // ... - } - onTerminate { - // 通过 this 得到当前 Application 实例 - // ... - } - onLowMemory { - // 通过 this 得到当前 Application 实例 - // ... - } - onTrimMemory { self, level -> - // 可在这里判断 APP 是否已切换到后台 - if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { - // ... - } - // ... - } - onConfigurationChanged { self, config -> - // ... - } - } -} -``` - -详情请参考 [AppLifecycle](api/document?id=applifecycle-class)。 - -### 注册系统广播 - -> 通过 `Application.onCreate` 方法注册系统广播,来实现对系统广播的监听。 - -我们还可以在宿主 `Application` 中注册系统广播。 - -> 示例如下 - -```kotlin -loadApp(name = "com.example.demo") { - // 注册生命周期监听 - onAppLifecycle { - // 注册用户解锁时的广播监听 - registerReceiver(Intent.ACTION_USER_PRESENT) { context, intent -> - // ... - } - // 注册多个广播监听 - 会同时回调多次 - registerReceiver(Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_TIME_TICK) { context, intent -> - // ... - } - } -} -``` - -详情请参考 [AppLifecycle](api/document?id=applifecycle-class)。 - -## 宿主资源注入扩展功能 - -> 这是一个将模块资源、`Activity` 组件以及 `Context` 主题注入到宿主的扩展功能。 - -在使用以下功能之前,为防止资源 ID 互相冲突,你需要在当前 Xposed 模块项目的 `build.gradle` 中修改资源 ID。 - -- Kotlin Gradle DSL - -```kotlin -android { - androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64") -} -``` - -- Groovy - -```groovy -android { - aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64' -} -``` - -!> 提供的示例资源 ID 值仅供参考,不可使用 `0x7f`,默认为 `0x64`,为了防止当前宿主存在多个 Xposed 模块,建议自定义你自己的资源 ID。 - -### 注入模块资源 (Resources) - -在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注入当前模块资源。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "onCreate" - param(BundleClass) - } - afterHook { - instance().also { - // <方案1> 通过 Context 注入模块资源 - it.injectModuleAppResources() - // <方案2> 直接得到宿主 Resources 注入模块资源 - it.resources.injectModuleAppResources() - // 直接使用模块资源 ID - it.getString(R.id.app_name) - } - } -} -``` - -你还可以直接在 `AppLifecycle` 中注入当前模块资源。 - -> 示例如下 - -```kotlin -onAppLifecycle { - onCreate { - // 全局注入模块资源,但仅限于全局生命周期,类似 ImageView.setImageResource 这样的方法在 Activity 中需要单独注入 - // <方案1> 通过 Context 注入模块资源 - injectModuleAppResources() - // <方案2> 直接得到宿主 Resources 注入模块资源 - resources.injectModuleAppResources() - // 直接使用模块资源 ID - getString(R.id.app_name) - } -} -``` - -详情请参考 [Context+Resources.injectModuleAppResources](api/document?id=contextresourcesinjectmoduleappresources-ext-method)。 - -### 注册模块 Activity - -在 Android 系统中所有应用的 `Activity` 启动时,都需要在 `AndroidManifest.xml` 中进行注册,在 Hook 过程中,如果我们想通过宿主来直接启动模块中未注册的 `Activity` 要怎么做呢? - -在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注册当前模块的 `Activity` 代理。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "onCreate" - param(BundleClass) - } - afterHook { - instance().registerModuleAppActivities() - } -} -``` - -你还可以直接在 `AppLifecycle` 中注册当前模块的 `Activity` 代理。 - -> 示例如下 - -```kotlin -onAppLifecycle { - onCreate { - registerModuleAppActivities() - } -} -``` - -如果没有填写 `proxy` 参数,API 将会根据当前 `Context` 自动获取当前宿主的启动入口 `Activity` 进行代理。 - -通常情况下,它是有效的,但是以上情况在一些 APP 中会失效,例如一些 `Activity` 会在注册清单上加入启动参数,那么我们就需要使用另一种解决方案。 - -若未注册的 `Activity` 不能被正确启动,我们可以手动拿到宿主的 `AndroidManifest.xml` 进行分析,来得到一个注册过的 `Activity` 标签,获取其中的 `name`。 - -你需要选择一个当前宿主可能用不到的、不需要的 `Activity` 作为一个“傀儡”将其进行代理,通常是有效的。 - -比如我们已经找到了能够被代理的合适 `Activity`。 - -> 示例如下 - -```xml - -``` - -根据其中的 `name`,我们只需要在方法中加入这个参数进行注册即可。 - -> 示例如下 - -```kotlin -registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity") -``` - -另一种情况,如果你对宿主的类编写了一个 `stub`,那么你可以直接通过 `Class` 对象来进行注册。 - -> 示例如下 - -```kotlin -registerModuleAppActivities(TestActivity::class.java) -``` - -注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 继承于 `ModuleAppActivity` 或 `ModuleAppCompatActivity`。 - -这些 `Activity` 现在无需注册即可无缝存活于宿主中。 - -> 示例如下 - -```kotlin -class HostTestActivity : ModuleAppActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // 模块资源已被自动注入,可以直接使用 xml 装载布局 - setContentView(R.layout.activity_main) - } -} -``` - -若你需要继承于 `ModuleAppCompatActivity`,你需要手动设置 AppCompat 主题。 - -> 示例如下 - -```kotlin -class HostTestActivity : ModuleAppCompatActivity() { - - // 这里的主题名称仅供参考,请填写你模块中已有的主题名称 - override val moduleTheme get() = R.style.Theme_AppCompat - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // 模块资源已被自动注入,可以直接使用 xml 装载布局 - setContentView(R.layout.activity_main) - } -} -``` - -以上步骤全部完成后,你就可以在 (Xposed) 宿主环境任意存在 `Context` 的地方愉快地调用 `startActivity` 了。 - -> 示例如下 - -```kotlin -val context: Context = ... // 假设这就是你的 Context -context.startActivity(context, HostTestActivity::class.java) -``` - -详情请参考 [Context.registerModuleAppActivities](api/document?id=contextregistermoduleappactivities-ext-method)。 - -### 创建 ContextThemeWrapper 代理 - -有时候,我们需要使用 `MaterialAlertDialogBuilder` 来美化自己在宿主中的对话框,但是拿不到 AppCompat 主题就无法创建。 - -- 会得到如下异常 - -``` -The style on this component requires your app theme to be Theme.AppCompat (or a descendant). -``` - -这时,我们想在宿主被 Hook 的当前 `Activity` 中使用 `MaterialAlertDialogBuilder` 来创建对话框,就可以有如下方法。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "onCreate" - param(BundleClass) - } - afterHook { - // 使用 applyModuleTheme 创建一个当前模块中的主题资源 - val appCompatContext = instance().applyModuleTheme(R.style.Theme_AppCompat) - // 直接使用这个包装了模块主题后的 Context 创建对话框 - MaterialAlertDialogBuilder(appCompatContext) - .setTitle("AppCompat 主题对话框") - .setMessage("我是一个在宿主中显示的 AppCompat 主题对话框。") - .setPositiveButton("确定", null) - .show() - } -} -``` - -你还可以对当前 `Context` 通过 `uiMode` 设置原生的夜间模式和日间模式,至少需要 Android 10 及以上系统版本支持且当前主题包含夜间模式相关元素。 - -> 示例如下 - -```kotlin -injectMember { - method { - name = "onCreate" - param(BundleClass) - } - afterHook { - // 定义当前模块中的主题资源 - var appCompatContext: ModuleContextThemeWrapper - // <方案1> 直接得到 Configuration 对象设置 - appCompatContext = instance().applyModuleTheme(R.style.Theme_AppCompat).applyConfiguration { uiMode = Configuration.UI_MODE_NIGHT_YES } - // <方案2> 创建一个新的 Configuration 对象,但会破坏当前宿主中原有的字体缩放大小等设置,你需要手动重新传递 densityDpi 等参数 - appCompatContext = instance().applyModuleTheme(R.style.Theme_AppCompat, Configuration().apply { uiMode = Configuration.UI_MODE_NIGHT_YES }) - // 直接使用这个包装了模块主题后的 Context 创建对话框 - MaterialAlertDialogBuilder(appCompatContext) - .setTitle("AppCompat 主题对话框") - .setMessage("我是一个在宿主中显示的 AppCompat 主题对话框。") - .setPositiveButton("确定", null) - .show() - } -} -``` - -这样,我们就可以在宿主中非常简单地使用 `MaterialAlertDialogBuilder` 创建对话框了。 - -- 可能存在的问题 - -由于一些 APP 自身使用的 `androidx` 依赖库或自定义主题可能会对当前 `MaterialAlertDialog` 实际样式造成干扰,你可以参考 [模块 APP Demo](https://github.com/fankes/YukiHookAPI/tree/master/demo-module) 来修复这个问题。 - -某些 APP 在创建时可能会发生 `ClassCastException` 异常,请手动指定新的 `Configuration` 实例来进行修复。 - -详情请参考 [Context.applyModuleTheme](api/document?id=contextapplymoduletheme-ext-method)。 - -

-[浏览下一篇  ➡️](guide/move-to-new-api.md) \ No newline at end of file diff --git a/docs/icon.png b/docs/icon.png deleted file mode 100644 index 7576340d..00000000 Binary files a/docs/icon.png and /dev/null differ diff --git a/docs/img-src/yukihookapi-projectbuilder.png b/docs/img-src/yukihookapi-projectbuilder.png deleted file mode 100644 index 854ab2a2..00000000 Binary files a/docs/img-src/yukihookapi-projectbuilder.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index b2c46f49..00000000 --- a/docs/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - YukiHookAPI - 轻量、高效、稳定的 Xposed Hook API - - - - - - - - - - - - -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/lib/docsify.js b/docs/lib/docsify.js deleted file mode 100644 index 41449458..00000000 --- a/docs/lib/docsify.js +++ /dev/null @@ -1,10051 +0,0 @@ -(function () { - /** - * Create a cached version of a pure function. - * @param {*} fn The function call to be cached - * @void - */ - - function cached(fn) { - var cache = Object.create(null); - return function (str) { - var key = isPrimitive(str) ? str : JSON.stringify(str); - var hit = cache[key]; - return hit || (cache[key] = fn(str)); - }; - } - - /** - * Hyphenate a camelCase string. - */ - var hyphenate = cached(function (str) { - return str.replace(/([A-Z])/g, function (m) { return '-' + m.toLowerCase(); }); - }); - - var hasOwn = Object.prototype.hasOwnProperty; - - /** - * Simple Object.assign polyfill - * @param {Object} to The object to be merged with - * @returns {Object} The merged object - */ - var merge = - Object.assign || - function (to) { - var arguments$1 = arguments; - - for (var i = 1; i < arguments.length; i++) { - var from = Object(arguments$1[i]); - - for (var key in from) { - if (hasOwn.call(from, key)) { - to[key] = from[key]; - } - } - } - - return to; - }; - - /** - * Check if value is primitive - * @param {*} value Checks if a value is primitive - * @returns {Boolean} Result of the check - */ - function isPrimitive(value) { - return typeof value === 'string' || typeof value === 'number'; - } - - /** - * Performs no operation. - * @void - */ - function noop() {} - - /** - * Check if value is function - * @param {*} obj Any javascript object - * @returns {Boolean} True if the passed-in value is a function - */ - function isFn(obj) { - return typeof obj === 'function'; - } - - /** - * Check if url is external - * @param {String} string url - * @returns {Boolean} True if the passed-in url is external - */ - function isExternal(url) { - var match = url.match( - /^([^:/?#]+:)?(?:\/{2,}([^/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/ - ); - - if ( - typeof match[1] === 'string' && - match[1].length > 0 && - match[1].toLowerCase() !== location.protocol - ) { - return true; - } - if ( - typeof match[2] === 'string' && - match[2].length > 0 && - match[2].replace( - new RegExp( - ':(' + { 'http:': 80, 'https:': 443 }[location.protocol] + ')?$' - ), - '' - ) !== location.host - ) { - return true; - } - return false; - } - - var inBrowser = !false; - - var isMobile = document.body.clientWidth <= 600; - - /** - * @see https://github.com/MoOx/pjax/blob/master/lib/is-supported.js - */ - var supportsPushState = - - (function () { - // Borrowed wholesale from https://github.com/defunkt/jquery-pjax - return ( - window.history && - window.history.pushState && - window.history.replaceState && - // PushState isn’t reliable on iOS until 5. - !navigator.userAgent.match( - /((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/ - ) - ); - })(); - - var cacheNode = {}; - - /** - * Get Node - * @param {String|Element} el A DOM element - * @param {Boolean} noCache Flag to use or not use the cache - * @return {Element} The found node element - */ - function getNode(el, noCache) { - if ( noCache === void 0 ) noCache = false; - - if (typeof el === 'string') { - if (typeof window.Vue !== 'undefined') { - return find(el); - } - - el = noCache ? find(el) : cacheNode[el] || (cacheNode[el] = find(el)); - } - - return el; - } - - var $ = document; - - var body = $.body; - - var head = $.head; - - /** - * Find elements - * @param {String|Element} el The root element where to perform the search from - * @param {Element} node The query - * @returns {Element} The found DOM element - * @example - * find('nav') => document.querySelector('nav') - * find(nav, 'a') => nav.querySelector('a') - */ - function find(el, node) { - return node ? el.querySelector(node) : $.querySelector(el); - } - - /** - * Find all elements - * @param {String|Element} el The root element where to perform the search from - * @param {Element} node The query - * @returns {Array} An array of DOM elements - * @example - * findAll('a') => [].slice.call(document.querySelectorAll('a')) - * findAll(nav, 'a') => [].slice.call(nav.querySelectorAll('a')) - */ - function findAll(el, node) { - return [].slice.call( - node ? el.querySelectorAll(node) : $.querySelectorAll(el) - ); - } - - function create(node, tpl) { - node = $.createElement(node); - if (tpl) { - node.innerHTML = tpl; - } - - return node; - } - - function appendTo(target, el) { - return target.appendChild(el); - } - - function before(target, el) { - return target.insertBefore(el, target.children[0]); - } - - function on(el, type, handler) { - isFn(type) - ? window.addEventListener(el, type) - : el.addEventListener(type, handler); - } - - function off(el, type, handler) { - isFn(type) - ? window.removeEventListener(el, type) - : el.removeEventListener(type, handler); - } - - /** - * Toggle class - * @param {String|Element} el The element that needs the class to be toggled - * @param {Element} type The type of action to be performed on the classList (toggle by default) - * @param {String} val Name of the class to be toggled - * @void - * @example - * toggleClass(el, 'active') => el.classList.toggle('active') - * toggleClass(el, 'add', 'active') => el.classList.add('active') - */ - function toggleClass(el, type, val) { - el && el.classList[val ? type : 'toggle'](val || type); - } - - function style(content) { - appendTo(head, create('style', content)); - } - - /** - * Fork https://github.com/bendrucker/document-ready/blob/master/index.js - * @param {Function} callback The callbacack to be called when the page is loaded - * @returns {Number|void} If the page is already laoded returns the result of the setTimeout callback, - * otherwise it only attaches the callback to the DOMContentLoaded event - */ - function documentReady(callback, doc) { - if ( doc === void 0 ) doc = document; - - var state = doc.readyState; - - if (state === 'complete' || state === 'interactive') { - return setTimeout(callback, 0); - } - - doc.addEventListener('DOMContentLoaded', callback); - } - - var dom = /*#__PURE__*/Object.freeze({ - __proto__: null, - getNode: getNode, - $: $, - body: body, - head: head, - find: find, - findAll: findAll, - create: create, - appendTo: appendTo, - before: before, - on: on, - off: off, - toggleClass: toggleClass, - style: style, - documentReady: documentReady - }); - - var decode = decodeURIComponent; - var encode = encodeURIComponent; - - function parseQuery(query) { - var res = {}; - - query = query.trim().replace(/^(\?|#|&)/, ''); - - if (!query) { - return res; - } - - // Simple parse - query.split('&').forEach(function (param) { - var parts = param.replace(/\+/g, ' ').split('='); - - res[parts[0]] = parts[1] && decode(parts[1]); - }); - - return res; - } - - function stringifyQuery(obj, ignores) { - if ( ignores === void 0 ) ignores = []; - - var qs = []; - - for (var key in obj) { - if (ignores.indexOf(key) > -1) { - continue; - } - - qs.push( - obj[key] - ? ((encode(key)) + "=" + (encode(obj[key]))).toLowerCase() - : encode(key) - ); - } - - return qs.length ? ("?" + (qs.join('&'))) : ''; - } - - var isAbsolutePath = cached(function (path) { - return /(:|(\/{2}))/g.test(path); - }); - - var removeParams = cached(function (path) { - return path.split(/[?#]/)[0]; - }); - - var getParentPath = cached(function (path) { - if (/\/$/g.test(path)) { - return path; - } - - var matchingParts = path.match(/(\S*\/)[^/]+$/); - return matchingParts ? matchingParts[1] : ''; - }); - - var cleanPath = cached(function (path) { - return path.replace(/^\/+/, '/').replace(/([^:])\/{2,}/g, '$1/'); - }); - - var resolvePath = cached(function (path) { - var segments = path.replace(/^\//, '').split('/'); - var resolved = []; - for (var i = 0, len = segments.length; i < len; i++) { - var segment = segments[i]; - if (segment === '..') { - resolved.pop(); - } else if (segment !== '.') { - resolved.push(segment); - } - } - - return '/' + resolved.join('/'); - }); - - /** - * Normalises the URI path to handle the case where Docsify is - * hosted off explicit files, i.e. /index.html. This function - * eliminates any path segments that contain `#` fragments. - * - * This is used to map browser URIs to markdown file sources. - * - * For example: - * - * http://example.org/base/index.html#/blah - * - * would be mapped to: - * - * http://example.org/base/blah.md. - * - * See here for more information: - * - * https://github.com/docsifyjs/docsify/pull/1372 - * - * @param {string} path The URI path to normalise - * @return {string} { path, query } - */ - - function normaliseFragment(path) { - return path - .split('/') - .filter(function (p) { return p.indexOf('#') === -1; }) - .join('/'); - } - - function getPath() { - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - return cleanPath(args.map(normaliseFragment).join('/')); - } - - var replaceSlug = cached(function (path) { - return path.replace('#', '?id='); - }); - - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - var cached$1 = {}; - - function getAlias(path, alias, last) { - var match = Object.keys(alias).filter(function (key) { - var re = cached$1[key] || (cached$1[key] = new RegExp(("^" + key + "$"))); - return re.test(path) && path !== last; - })[0]; - - return match - ? getAlias(path.replace(cached$1[match], alias[match]), alias, path) - : path; - } - - function getFileName(path, ext) { - return new RegExp(("\\.(" + (ext.replace(/^\./, '')) + "|html)$"), 'g').test(path) - ? path - : /\/$/g.test(path) - ? (path + "README" + ext) - : ("" + path + ext); - } - - var History = function History(config) { - this.config = config; - }; - - History.prototype.getBasePath = function getBasePath () { - return this.config.basePath; - }; - - History.prototype.getFile = function getFile (path, isRelative) { - if ( path === void 0 ) path = this.getCurrentPath(); - - var ref = this; - var config = ref.config; - var base = this.getBasePath(); - var ext = typeof config.ext === 'string' ? config.ext : '.md'; - - path = config.alias ? getAlias(path, config.alias) : path; - path = getFileName(path, ext); - path = path === ("/README" + ext) ? config.homepage || path : path; - path = isAbsolutePath(path) ? path : getPath(base, path); - - if (isRelative) { - path = path.replace(new RegExp(("^" + base)), ''); - } - - return path; - }; - - History.prototype.onchange = function onchange (cb) { - if ( cb === void 0 ) cb = noop; - - cb(); - }; - - History.prototype.getCurrentPath = function getCurrentPath () {}; - - History.prototype.normalize = function normalize () {}; - - History.prototype.parse = function parse () {}; - - History.prototype.toURL = function toURL (path, params, currentRoute) { - var local = currentRoute && path[0] === '#'; - var route = this.parse(replaceSlug(path)); - - route.query = merge({}, route.query, params); - path = route.path + stringifyQuery(route.query); - path = path.replace(/\.md(\?)|\.md$/, '$1'); - - if (local) { - var idIndex = currentRoute.indexOf('?'); - path = - (idIndex > 0 ? currentRoute.substring(0, idIndex) : currentRoute) + - path; - } - - if (this.config.relativePath && path.indexOf('/') !== 0) { - var currentDir = currentRoute.substring( - 0, - currentRoute.lastIndexOf('/') + 1 - ); - return cleanPath(resolvePath(currentDir + path)); - } - - return cleanPath('/' + path); - }; - - function replaceHash(path) { - var i = location.href.indexOf('#'); - location.replace(location.href.slice(0, i >= 0 ? i : 0) + '#' + path); - } - var HashHistory = /*@__PURE__*/(function (History) { - function HashHistory(config) { - History.call(this, config); - this.mode = 'hash'; - } - - if ( History ) HashHistory.__proto__ = History; - HashHistory.prototype = Object.create( History && History.prototype ); - HashHistory.prototype.constructor = HashHistory; - - HashHistory.prototype.getBasePath = function getBasePath () { - var path = window.location.pathname || ''; - var base = this.config.basePath; - - // This handles the case where Docsify is served off an - // explicit file path, i.e.`/base/index.html#/blah`. This - // prevents the `/index.html` part of the URI from being - // remove during routing. - // See here: https://github.com/docsifyjs/docsify/pull/1372 - var basePath = endsWith(path, '.html') - ? path + '#/' + base - : path + '/' + base; - return /^(\/|https?:)/g.test(base) ? base : cleanPath(basePath); - }; - - HashHistory.prototype.getCurrentPath = function getCurrentPath () { - // We can't use location.hash here because it's not - // consistent across browsers - Firefox will pre-decode it! - var href = location.href; - var index = href.indexOf('#'); - return index === -1 ? '' : href.slice(index + 1); - }; - - /** @param {((params: {source: TODO}) => void)} [cb] */ - HashHistory.prototype.onchange = function onchange (cb) { - if ( cb === void 0 ) cb = noop; - - // The hashchange event does not tell us if it originated from - // a clicked link or by moving back/forward in the history; - // therefore we set a `navigating` flag when a link is clicked - // to be able to tell these two scenarios apart - var navigating = false; - - on('click', function (e) { - var el = e.target.tagName === 'A' ? e.target : e.target.parentNode; - - if (el && el.tagName === 'A' && !/_blank/.test(el.target)) { - navigating = true; - } - }); - - on('hashchange', function (e) { - var source = navigating ? 'navigate' : 'history'; - navigating = false; - cb({ event: e, source: source }); - }); - }; - - HashHistory.prototype.normalize = function normalize () { - var path = this.getCurrentPath(); - - path = replaceSlug(path); - - if (path.charAt(0) === '/') { - return replaceHash(path); - } - - replaceHash('/' + path); - }; - - /** - * Parse the url - * @param {string} [path=location.herf] URL to be parsed - * @return {object} { path, query } - */ - HashHistory.prototype.parse = function parse (path) { - if ( path === void 0 ) path = location.href; - - var query = ''; - - var hashIndex = path.indexOf('#'); - if (hashIndex >= 0) { - path = path.slice(hashIndex + 1); - } - - var queryIndex = path.indexOf('?'); - if (queryIndex >= 0) { - query = path.slice(queryIndex + 1); - path = path.slice(0, queryIndex); - } - - return { - path: path, - file: this.getFile(path, true), - query: parseQuery(query), - }; - }; - - HashHistory.prototype.toURL = function toURL (path, params, currentRoute) { - return '#' + History.prototype.toURL.call(this, path, params, currentRoute); - }; - - return HashHistory; - }(History)); - - /** @typedef {any} TODO */ - - var HTML5History = /*@__PURE__*/(function (History) { - function HTML5History(config) { - History.call(this, config); - this.mode = 'history'; - } - - if ( History ) HTML5History.__proto__ = History; - HTML5History.prototype = Object.create( History && History.prototype ); - HTML5History.prototype.constructor = HTML5History; - - HTML5History.prototype.getCurrentPath = function getCurrentPath () { - var base = this.getBasePath(); - var path = window.location.pathname; - - if (base && path.indexOf(base) === 0) { - path = path.slice(base.length); - } - - return (path || '/') + window.location.search + window.location.hash; - }; - - HTML5History.prototype.onchange = function onchange (cb) { - var this$1 = this; - if ( cb === void 0 ) cb = noop; - - on('click', function (e) { - var el = e.target.tagName === 'A' ? e.target : e.target.parentNode; - - if (el && el.tagName === 'A' && !/_blank/.test(el.target)) { - e.preventDefault(); - var url = el.href; - // solve history.pushState cross-origin issue - if (this$1.config.crossOriginLinks.indexOf(url) !== -1) { - window.open(url, '_self'); - } else { - window.history.pushState({ key: url }, '', url); - } - cb({ event: e, source: 'navigate' }); - } - }); - - on('popstate', function (e) { - cb({ event: e, source: 'history' }); - }); - }; - - /** - * Parse the url - * @param {string} [path=location.href] URL to be parsed - * @return {object} { path, query } - */ - HTML5History.prototype.parse = function parse (path) { - if ( path === void 0 ) path = location.href; - - var query = ''; - - var queryIndex = path.indexOf('?'); - if (queryIndex >= 0) { - query = path.slice(queryIndex + 1); - path = path.slice(0, queryIndex); - } - - var base = getPath(location.origin); - var baseIndex = path.indexOf(base); - - if (baseIndex > -1) { - path = path.slice(baseIndex + base.length); - } - - return { - path: path, - file: this.getFile(path), - query: parseQuery(query), - }; - }; - - return HTML5History; - }(History)); - - /** - * @typedef {{ - * path?: string - * }} Route - */ - - /** @type {Route} */ - var lastRoute = {}; - - /** @typedef {import('../Docsify').Constructor} Constructor */ - - /** - * @template {!Constructor} T - * @param {T} Base - The class to extend - */ - function Router(Base) { - return /*@__PURE__*/(function (Base) { - function Router() { - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - Base.apply(this, args); - - this.route = {}; - } - - if ( Base ) Router.__proto__ = Base; - Router.prototype = Object.create( Base && Base.prototype ); - Router.prototype.constructor = Router; - - Router.prototype.updateRender = function updateRender () { - this.router.normalize(); - this.route = this.router.parse(); - body.setAttribute('data-page', this.route.file); - }; - - Router.prototype.initRouter = function initRouter () { - var this$1 = this; - - var config = this.config; - var mode = config.routerMode || 'hash'; - var router; - - if (mode === 'history' && supportsPushState) { - router = new HTML5History(config); - } else { - router = new HashHistory(config); - } - - this.router = router; - this.updateRender(); - lastRoute = this.route; - - // eslint-disable-next-line no-unused-vars - router.onchange(function (params) { - this$1.updateRender(); - this$1._updateRender(); - - if (lastRoute.path === this$1.route.path) { - this$1.$resetEvents(params.source); - return; - } - - this$1.$fetch(noop, this$1.$resetEvents.bind(this$1, params.source)); - lastRoute = this$1.route; - }); - }; - - return Router; - }(Base)); - } - - var RGX = /([^{]*?)\w(?=\})/g; - - var MAP = { - YYYY: 'getFullYear', - YY: 'getYear', - MM: function (d) { - return d.getMonth() + 1; - }, - DD: 'getDate', - HH: 'getHours', - mm: 'getMinutes', - ss: 'getSeconds', - fff: 'getMilliseconds' - }; - - function tinydate (str, custom) { - var parts=[], offset=0; - - str.replace(RGX, function (key, _, idx) { - // save preceding string - parts.push(str.substring(offset, idx - 1)); - offset = idx += key.length + 1; - // save function - parts.push(custom && custom[key] || function (d) { - return ('00' + (typeof MAP[key] === 'string' ? d[MAP[key]]() : MAP[key](d))).slice(-key.length); - }); - }); - - if (offset !== str.length) { - parts.push(str.substring(offset)); - } - - return function (arg) { - var out='', i=0, d=arg||new Date(); - for (; i 80 ? 80 : num; - } else { - num = Math.floor((loaded / total) * 100); - } - - barEl.style.opacity = 1; - barEl.style.width = num >= 95 ? '100%' : num + '%'; - - if (num >= 95) { - clearTimeout(timeId); - // eslint-disable-next-line no-unused-vars - timeId = setTimeout(function (_) { - barEl.style.opacity = 0; - barEl.style.width = '0%'; - }, 200); - } - } - - /* eslint-disable no-unused-vars */ - - var cache = {}; - - /** - * Ajax GET implmentation - * @param {string} url Resource URL - * @param {boolean} [hasBar=false] Has progress bar - * @param {String[]} headers Array of headers - * @return {Promise} Promise response - */ - function get(url, hasBar, headers) { - if ( hasBar === void 0 ) hasBar = false; - if ( headers === void 0 ) headers = {}; - - var xhr = new XMLHttpRequest(); - var on = function () { - xhr.addEventListener.apply(xhr, arguments); - }; - - // if (cached) { - // return { then: cb => cb(cached.content, cached.opt), abort: noop }; - // } - - xhr.open('GET', url); - for (var i in headers) { - if (hasOwn.call(headers, i)) { - xhr.setRequestHeader(i, headers[i]); - } - } - - xhr.send(); - - return { - then: function (success, error) { - if ( error === void 0 ) error = noop; - - if (hasBar) { - var id = setInterval( - function (_) { return progressbar({ - step: Math.floor(Math.random() * 5 + 1), - }); }, - 500 - ); - - on('progress', progressbar); - on('loadend', function (evt) { - progressbar(evt); - clearInterval(id); - }); - } - - on('error', error); - on('load', function (ref) { - var target = ref.target; - - if (target.status >= 400) { - error(target); - } else { - var result = (cache[url] = { - content: target.response, - opt: { - updatedAt: xhr.getResponseHeader('last-modified'), - }, - }); - - success(result.content, result.opt); - } - }); - }, - abort: function (_) { return xhr.readyState !== 4 && xhr.abort(); }, - }; - } - - function replaceVar(block, color) { - block.innerHTML = block.innerHTML.replace( - /var\(\s*--theme-color.*?\)/g, - color - ); - } - - function cssVars (color) { - // Variable support - if (window.CSS && window.CSS.supports && window.CSS.supports('(--v:red)')) { - return; - } - - var styleBlocks = findAll('style:not(.inserted),link'); - [].forEach.call(styleBlocks, function (block) { - if (block.nodeName === 'STYLE') { - replaceVar(block, color); - } else if (block.nodeName === 'LINK') { - var href = block.getAttribute('href'); - - if (!/\.css$/.test(href)) { - return; - } - - get(href).then(function (res) { - var style = create('style', res); - - head.appendChild(style); - replaceVar(style, color); - }); - } - }); - } - - /* eslint-disable no-unused-vars */ - - var title = $.title; - /** - * Toggle button - * @param {Element} el Button to be toggled - * @void - */ - function btn(el) { - var toggle = function (_) { return body.classList.toggle('close'); }; - - el = getNode(el); - if (el === null || el === undefined) { - return; - } - - on(el, 'click', function (e) { - e.stopPropagation(); - toggle(); - }); - - isMobile && - on( - body, - 'click', - function (_) { return body.classList.contains('close') && toggle(); } - ); - } - - function collapse(el) { - el = getNode(el); - if (el === null || el === undefined) { - return; - } - - on(el, 'click', function (ref) { - var target = ref.target; - - if ( - target.nodeName === 'A' && - target.nextSibling && - target.nextSibling.classList && - target.nextSibling.classList.contains('app-sub-sidebar') - ) { - toggleClass(target.parentNode, 'collapse'); - } - }); - } - - function sticky() { - var cover = getNode('section.cover'); - if (!cover) { - return; - } - - var coverHeight = cover.getBoundingClientRect().height; - - if (window.pageYOffset >= coverHeight || cover.classList.contains('hidden')) { - toggleClass(body, 'add', 'sticky'); - } else { - toggleClass(body, 'remove', 'sticky'); - } - } - - /** - * Get and active link - * @param {Object} router Router - * @param {String|Element} el Target element - * @param {Boolean} isParent Active parent - * @param {Boolean} autoTitle Automatically set title - * @return {Element} Active element - */ - function getAndActive(router, el, isParent, autoTitle) { - el = getNode(el); - var links = []; - if (el !== null && el !== undefined) { - links = findAll(el, 'a'); - } - - var hash = decodeURI(router.toURL(router.getCurrentPath())); - var target; - - links - .sort(function (a, b) { return b.href.length - a.href.length; }) - .forEach(function (a) { - var href = decodeURI(a.getAttribute('href')); - var node = isParent ? a.parentNode : a; - - a.title = a.title || a.innerText; - - if (hash.indexOf(href) === 0 && !target) { - target = a; - toggleClass(node, 'add', 'active'); - } else { - toggleClass(node, 'remove', 'active'); - } - }); - - if (autoTitle) { - $.title = target - ? target.title || ((target.innerText) + " - " + title) - : title; - } - - return target; - } - - var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); - - function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var SingleTweener = function () { - function SingleTweener() { - var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - _classCallCheck(this, SingleTweener); - - this.start = opts.start; - this.end = opts.end; - this.decimal = opts.decimal; - } - - _createClass(SingleTweener, [{ - key: "getIntermediateValue", - value: function getIntermediateValue(tick) { - if (this.decimal) { - return tick; - } else { - return Math.round(tick); - } - } - }, { - key: "getFinalValue", - value: function getFinalValue() { - return this.end; - } - }]); - - return SingleTweener; - }(); - - var _createClass$1 = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) { descriptor.writable = true; } Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) { defineProperties(Constructor.prototype, protoProps); } if (staticProps) { defineProperties(Constructor, staticProps); } return Constructor; }; }(); - - function _classCallCheck$1(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - - var Tweezer = function () { - function Tweezer() { - var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - - _classCallCheck$1(this, Tweezer); - - this.duration = opts.duration || 1000; - this.ease = opts.easing || this._defaultEase; - this.tweener = opts.tweener || new SingleTweener(opts); - this.start = this.tweener.start; - this.end = this.tweener.end; - - this.frame = null; - this.next = null; - this.isRunning = false; - this.events = {}; - this.direction = this.start < this.end ? 'up' : 'down'; - } - - _createClass$1(Tweezer, [{ - key: 'begin', - value: function begin() { - if (!this.isRunning && this.next !== this.end) { - this.frame = window.requestAnimationFrame(this._tick.bind(this)); - } - return this; - } - }, { - key: 'stop', - value: function stop() { - window.cancelAnimationFrame(this.frame); - this.isRunning = false; - this.frame = null; - this.timeStart = null; - this.next = null; - return this; - } - }, { - key: 'on', - value: function on(name, handler) { - this.events[name] = this.events[name] || []; - this.events[name].push(handler); - return this; - } - }, { - key: '_emit', - value: function _emit(name, val) { - var _this = this; - - var e = this.events[name]; - e && e.forEach(function (handler) { - return handler.call(_this, val); - }); - } - }, { - key: '_tick', - value: function _tick(currentTime) { - this.isRunning = true; - - var lastTick = this.next || this.start; - - if (!this.timeStart) { this.timeStart = currentTime; } - this.timeElapsed = currentTime - this.timeStart; - this.next = this.ease(this.timeElapsed, this.start, this.end - this.start, this.duration); - - if (this._shouldTick(lastTick)) { - this._emit('tick', this.tweener.getIntermediateValue(this.next)); - this.frame = window.requestAnimationFrame(this._tick.bind(this)); - } else { - this._emit('tick', this.tweener.getFinalValue()); - this._emit('done', null); - } - } - }, { - key: '_shouldTick', - value: function _shouldTick(lastTick) { - return { - up: this.next < this.end && lastTick <= this.next, - down: this.next > this.end && lastTick >= this.next - }[this.direction]; - } - }, { - key: '_defaultEase', - value: function _defaultEase(t, b, c, d) { - if ((t /= d / 2) < 1) { return c / 2 * t * t + b; } - return -c / 2 * (--t * (t - 2) - 1) + b; - } - }]); - - return Tweezer; - }(); - - var currentScript = document.currentScript; - - /** @param {import('./Docsify').Docsify} vm */ - function config (vm) { - var config = merge( - { - auto2top: false, - autoHeader: false, - basePath: '', - catchPluginErrors: true, - cornerExternalLinkTarget: '_blank', - coverpage: '', - crossOriginLinks: [], - el: '#app', - executeScript: null, - ext: '.md', - externalLinkRel: 'noopener', - externalLinkTarget: '_blank', - formatUpdated: '', - ga: '', - homepage: 'README.md', - loadNavbar: null, - loadSidebar: null, - maxLevel: 6, - mergeNavbar: false, - name: '', - nameLink: window.location.pathname, - nativeEmoji: false, - noCompileLinks: [], - noEmoji: false, - notFoundPage: true, - relativePath: false, - repo: '', - routerMode: 'hash', - subMaxLevel: 0, - themeColor: '', - topMargin: 0, - }, - typeof window.$docsify === 'function' - ? window.$docsify(vm) - : window.$docsify - ); - - var script = - currentScript || - [].slice - .call(document.getElementsByTagName('script')) - .filter(function (n) { return /docsify\./.test(n.src); })[0]; - - if (script) { - for (var prop in config) { - if (hasOwn.call(config, prop)) { - var val = script.getAttribute('data-' + hyphenate(prop)); - - if (isPrimitive(val)) { - config[prop] = val === '' ? true : val; - } - } - } - } - - if (config.loadSidebar === true) { - config.loadSidebar = '_sidebar' + config.ext; - } - - if (config.loadNavbar === true) { - config.loadNavbar = '_navbar' + config.ext; - } - - if (config.coverpage === true) { - config.coverpage = '_coverpage' + config.ext; - } - - if (config.repo === true) { - config.repo = ''; - } - - if (config.name === true) { - config.name = ''; - } - - window.$docsify = config; - - return config; - } - - var nav = {}; - var hoverOver = false; - var scroller = null; - var enableScrollEvent = true; - var coverHeight = 0; - - function scrollTo(el, offset) { - if ( offset === void 0 ) offset = 0; - - if (scroller) { - scroller.stop(); - } - - enableScrollEvent = false; - scroller = new Tweezer({ - start: window.pageYOffset, - end: - Math.round(el.getBoundingClientRect().top) + window.pageYOffset - offset, - duration: 500, - }) - .on('tick', function (v) { return window.scrollTo(0, v); }) - .on('done', function () { - enableScrollEvent = true; - scroller = null; - }) - .begin(); - } - - function highlight(path) { - if (!enableScrollEvent) { - return; - } - - var sidebar = getNode('.sidebar'); - var anchors = findAll('.anchor'); - var wrap = find(sidebar, '.sidebar-nav'); - var active = find(sidebar, 'li.active'); - var doc = document.documentElement; - var top = ((doc && doc.scrollTop) || document.body.scrollTop) - coverHeight; - var last; - - for (var i = 0, len = anchors.length; i < len; i += 1) { - var node = anchors[i]; - - if (node.offsetTop > top) { - if (!last) { - last = node; - } - - break; - } else { - last = node; - } - } - - if (!last) { - return; - } - - var li = nav[getNavKey(path, last.getAttribute('data-id'))]; - - if (!li || li === active) { - return; - } - - active && active.classList.remove('active'); - li.classList.add('active'); - active = li; - - // Scroll into view - // https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297 - if (!hoverOver && body.classList.contains('sticky')) { - var height = sidebar.clientHeight; - var curOffset = 0; - var cur = active.offsetTop + active.clientHeight + 40; - var isInView = - active.offsetTop >= wrap.scrollTop && cur <= wrap.scrollTop + height; - var notThan = cur - curOffset < height; - - sidebar.scrollTop = isInView - ? wrap.scrollTop - : notThan - ? curOffset - : cur - height; - } - } - - function getNavKey(path, id) { - return ((decodeURIComponent(path)) + "?id=" + (decodeURIComponent(id))); - } - - function scrollActiveSidebar(router) { - var cover = find('.cover.show'); - coverHeight = cover ? cover.offsetHeight : 0; - - var sidebar = getNode('.sidebar'); - var lis = []; - if (sidebar !== null && sidebar !== undefined) { - lis = findAll(sidebar, 'li'); - } - - for (var i = 0, len = lis.length; i < len; i += 1) { - var li = lis[i]; - var a = li.querySelector('a'); - if (!a) { - continue; - } - - var href = a.getAttribute('href'); - - if (href !== '/') { - var ref = router.parse(href); - var id = ref.query.id; - var path$1 = ref.path; - if (id) { - href = getNavKey(path$1, id); - } - } - - if (href) { - nav[decodeURIComponent(href)] = li; - } - } - - if (isMobile) { - return; - } - - var path = removeParams(router.getCurrentPath()); - off('scroll', function () { return highlight(path); }); - on('scroll', function () { return highlight(path); }); - on(sidebar, 'mouseover', function () { - hoverOver = true; - }); - on(sidebar, 'mouseleave', function () { - hoverOver = false; - }); - } - - function scrollIntoView(path, id) { - if (!id) { - return; - } - var topMargin = config().topMargin; - var section = find('#' + id); - section && scrollTo(section, topMargin); - - var li = nav[getNavKey(path, id)]; - var sidebar = getNode('.sidebar'); - var active = find(sidebar, 'li.active'); - active && active.classList.remove('active'); - li && li.classList.add('active'); - } - - var scrollEl = $.scrollingElement || $.documentElement; - - function scroll2Top(offset) { - if ( offset === void 0 ) offset = 0; - - scrollEl.scrollTop = offset === true ? 0 : Number(offset); - } - - var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - - function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; - } - - var defaults = createCommonjsModule(function (module) { - function getDefaults() { - return { - baseUrl: null, - breaks: false, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: null, - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - tokenizer: null, - walkTokens: null, - xhtml: false - }; - } - - function changeDefaults(newDefaults) { - module.exports.defaults = newDefaults; - } - - module.exports = { - defaults: getDefaults(), - getDefaults: getDefaults, - changeDefaults: changeDefaults - }; - }); - var defaults_1 = defaults.defaults; - var defaults_2 = defaults.getDefaults; - var defaults_3 = defaults.changeDefaults; - - /** - * Helpers - */ - var escapeTest = /[&<>"']/; - var escapeReplace = /[&<>"']/g; - var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; - var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; - var escapeReplacements = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - var getEscapeReplacement = function (ch) { return escapeReplacements[ch]; }; - function escape(html, encode) { - if (encode) { - if (escapeTest.test(html)) { - return html.replace(escapeReplace, getEscapeReplacement); - } - } else { - if (escapeTestNoEncode.test(html)) { - return html.replace(escapeReplaceNoEncode, getEscapeReplacement); - } - } - - return html; - } - - var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; - - function unescape(html) { - // explicitly match decimal, hex, and named HTML entities - return html.replace(unescapeTest, function (_, n) { - n = n.toLowerCase(); - if (n === 'colon') { return ':'; } - if (n.charAt(0) === '#') { - return n.charAt(1) === 'x' - ? String.fromCharCode(parseInt(n.substring(2), 16)) - : String.fromCharCode(+n.substring(1)); - } - return ''; - }); - } - - var caret = /(^|[^\[])\^/g; - function edit(regex, opt) { - regex = regex.source || regex; - opt = opt || ''; - var obj = { - replace: function (name, val) { - val = val.source || val; - val = val.replace(caret, '$1'); - regex = regex.replace(name, val); - return obj; - }, - getRegex: function () { - return new RegExp(regex, opt); - } - }; - return obj; - } - - var nonWordAndColonTest = /[^\w:]/g; - var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; - function cleanUrl(sanitize, base, href) { - if (sanitize) { - var prot; - try { - prot = decodeURIComponent(unescape(href)) - .replace(nonWordAndColonTest, '') - .toLowerCase(); - } catch (e) { - return null; - } - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return null; - } - } - if (base && !originIndependentUrl.test(href)) { - href = resolveUrl(base, href); - } - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { - return null; - } - return href; - } - - var baseUrls = {}; - var justDomain = /^[^:]+:\/*[^/]*$/; - var protocol = /^([^:]+:)[\s\S]*$/; - var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/; - - function resolveUrl(base, href) { - if (!baseUrls[' ' + base]) { - // we can ignore everything in base after the last slash of its path component, - // but we might need to add _that_ - // https://tools.ietf.org/html/rfc3986#section-3 - if (justDomain.test(base)) { - baseUrls[' ' + base] = base + '/'; - } else { - baseUrls[' ' + base] = rtrim(base, '/', true); - } - } - base = baseUrls[' ' + base]; - var relativeBase = base.indexOf(':') === -1; - - if (href.substring(0, 2) === '//') { - if (relativeBase) { - return href; - } - return base.replace(protocol, '$1') + href; - } else if (href.charAt(0) === '/') { - if (relativeBase) { - return href; - } - return base.replace(domain, '$1') + href; - } else { - return base + href; - } - } - - var noopTest = { exec: function noopTest() {} }; - - function merge$1(obj) { - var arguments$1 = arguments; - - var i = 1, - target, - key; - - for (; i < arguments.length; i++) { - target = arguments$1[i]; - for (key in target) { - if (Object.prototype.hasOwnProperty.call(target, key)) { - obj[key] = target[key]; - } - } - } - - return obj; - } - - function splitCells(tableRow, count) { - // ensure that every cell-delimiting pipe has a space - // before it to distinguish it from an escaped pipe - var row = tableRow.replace(/\|/g, function (match, offset, str) { - var escaped = false, - curr = offset; - while (--curr >= 0 && str[curr] === '\\') { escaped = !escaped; } - if (escaped) { - // odd number of slashes means | is escaped - // so we leave it alone - return '|'; - } else { - // add space before unescaped | - return ' |'; - } - }), - cells = row.split(/ \|/); - var i = 0; - - if (cells.length > count) { - cells.splice(count); - } else { - while (cells.length < count) { cells.push(''); } - } - - for (; i < cells.length; i++) { - // leading or trailing whitespace is ignored per the gfm spec - cells[i] = cells[i].trim().replace(/\\\|/g, '|'); - } - return cells; - } - - // Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). - // /c*$/ is vulnerable to REDOS. - // invert: Remove suffix of non-c chars instead. Default falsey. - function rtrim(str, c, invert) { - var l = str.length; - if (l === 0) { - return ''; - } - - // Length of suffix matching the invert condition. - var suffLen = 0; - - // Step left until we fail to match the invert condition. - while (suffLen < l) { - var currChar = str.charAt(l - suffLen - 1); - if (currChar === c && !invert) { - suffLen++; - } else if (currChar !== c && invert) { - suffLen++; - } else { - break; - } - } - - return str.substr(0, l - suffLen); - } - - function findClosingBracket(str, b) { - if (str.indexOf(b[1]) === -1) { - return -1; - } - var l = str.length; - var level = 0, - i = 0; - for (; i < l; i++) { - if (str[i] === '\\') { - i++; - } else if (str[i] === b[0]) { - level++; - } else if (str[i] === b[1]) { - level--; - if (level < 0) { - return i; - } - } - } - return -1; - } - - function checkSanitizeDeprecation(opt) { - if (opt && opt.sanitize && !opt.silent) { - console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options'); - } - } - - // copied from https://stackoverflow.com/a/5450113/806777 - function repeatString(pattern, count) { - if (count < 1) { - return ''; - } - var result = ''; - while (count > 1) { - if (count & 1) { - result += pattern; - } - count >>= 1; - pattern += pattern; - } - return result + pattern; - } - - var helpers = { - escape: escape, - unescape: unescape, - edit: edit, - cleanUrl: cleanUrl, - resolveUrl: resolveUrl, - noopTest: noopTest, - merge: merge$1, - splitCells: splitCells, - rtrim: rtrim, - findClosingBracket: findClosingBracket, - checkSanitizeDeprecation: checkSanitizeDeprecation, - repeatString: repeatString - }; - - var defaults$1 = defaults.defaults; - - var rtrim$1 = helpers.rtrim; - var splitCells$1 = helpers.splitCells; - var escape$1 = helpers.escape; - var findClosingBracket$1 = helpers.findClosingBracket; - - function outputLink(cap, link, raw) { - var href = link.href; - var title = link.title ? escape$1(link.title) : null; - var text = cap[1].replace(/\\([\[\]])/g, '$1'); - - if (cap[0].charAt(0) !== '!') { - return { - type: 'link', - raw: raw, - href: href, - title: title, - text: text - }; - } else { - return { - type: 'image', - raw: raw, - href: href, - title: title, - text: escape$1(text) - }; - } - } - - function indentCodeCompensation(raw, text) { - var matchIndentToCode = raw.match(/^(\s+)(?:```)/); - - if (matchIndentToCode === null) { - return text; - } - - var indentToCode = matchIndentToCode[1]; - - return text - .split('\n') - .map(function (node) { - var matchIndentInNode = node.match(/^\s+/); - if (matchIndentInNode === null) { - return node; - } - - var indentInNode = matchIndentInNode[0]; - - if (indentInNode.length >= indentToCode.length) { - return node.slice(indentToCode.length); - } - - return node; - }) - .join('\n'); - } - - /** - * Tokenizer - */ - var Tokenizer = /*@__PURE__*/(function () { - function Tokenizer(options) { - this.options = options || defaults$1; - } - - Tokenizer.prototype.space = function space (src) { - var cap = this.rules.block.newline.exec(src); - if (cap) { - if (cap[0].length > 1) { - return { - type: 'space', - raw: cap[0] - }; - } - return { raw: '\n' }; - } - }; - - Tokenizer.prototype.code = function code (src, tokens) { - var cap = this.rules.block.code.exec(src); - if (cap) { - var lastToken = tokens[tokens.length - 1]; - // An indented code block cannot interrupt a paragraph. - if (lastToken && lastToken.type === 'paragraph') { - return { - raw: cap[0], - text: cap[0].trimRight() - }; - } - - var text = cap[0].replace(/^ {1,4}/gm, ''); - return { - type: 'code', - raw: cap[0], - codeBlockStyle: 'indented', - text: !this.options.pedantic - ? rtrim$1(text, '\n') - : text - }; - } - }; - - Tokenizer.prototype.fences = function fences (src) { - var cap = this.rules.block.fences.exec(src); - if (cap) { - var raw = cap[0]; - var text = indentCodeCompensation(raw, cap[3] || ''); - - return { - type: 'code', - raw: raw, - lang: cap[2] ? cap[2].trim() : cap[2], - text: text - }; - } - }; - - Tokenizer.prototype.heading = function heading (src) { - var cap = this.rules.block.heading.exec(src); - if (cap) { - var text = cap[2].trim(); - - // remove trailing #s - if (/#$/.test(text)) { - var trimmed = rtrim$1(text, '#'); - if (this.options.pedantic) { - text = trimmed.trim(); - } else if (!trimmed || / $/.test(trimmed)) { - // CommonMark requires space before trailing #s - text = trimmed.trim(); - } - } - - return { - type: 'heading', - raw: cap[0], - depth: cap[1].length, - text: text - }; - } - }; - - Tokenizer.prototype.nptable = function nptable (src) { - var cap = this.rules.block.nptable.exec(src); - if (cap) { - var item = { - type: 'table', - header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [], - raw: cap[0] - }; - - if (item.header.length === item.align.length) { - var l = item.align.length; - var i; - for (i = 0; i < l; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - l = item.cells.length; - for (i = 0; i < l; i++) { - item.cells[i] = splitCells$1(item.cells[i], item.header.length); - } - - return item; - } - } - }; - - Tokenizer.prototype.hr = function hr (src) { - var cap = this.rules.block.hr.exec(src); - if (cap) { - return { - type: 'hr', - raw: cap[0] - }; - } - }; - - Tokenizer.prototype.blockquote = function blockquote (src) { - var cap = this.rules.block.blockquote.exec(src); - if (cap) { - var text = cap[0].replace(/^ *> ?/gm, ''); - - return { - type: 'blockquote', - raw: cap[0], - text: text - }; - } - }; - - Tokenizer.prototype.list = function list (src) { - var cap = this.rules.block.list.exec(src); - if (cap) { - var raw = cap[0]; - var bull = cap[2]; - var isordered = bull.length > 1; - - var list = { - type: 'list', - raw: raw, - ordered: isordered, - start: isordered ? +bull.slice(0, -1) : '', - loose: false, - items: [] - }; - - // Get each top-level item. - var itemMatch = cap[0].match(this.rules.block.item); - - var next = false, - item, - space, - bcurr, - bnext, - addBack, - loose, - istask, - ischecked; - - var l = itemMatch.length; - bcurr = this.rules.block.listItemStart.exec(itemMatch[0]); - for (var i = 0; i < l; i++) { - item = itemMatch[i]; - raw = item; - - // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - if (i !== l - 1) { - bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]); - if ( - !this.options.pedantic - ? bnext[1].length > bcurr[0].length || bnext[1].length > 3 - : bnext[1].length > bcurr[1].length - ) { - // nested list - itemMatch.splice(i, 2, itemMatch[i] + '\n' + itemMatch[i + 1]); - i--; - l--; - continue; - } else { - if ( - // different bullet style - !this.options.pedantic || this.options.smartLists - ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1] - : isordered === (bnext[2].length === 1) - ) { - addBack = itemMatch.slice(i + 1).join('\n'); - list.raw = list.raw.substring(0, list.raw.length - addBack.length); - i = l - 1; - } - } - bcurr = bnext; - } - - // Remove the list item's bullet - // so it is seen as the next token. - space = item.length; - item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); - - // Outdent whatever the - // list item contains. Hacky. - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic - ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') - : item.replace(/^ {1,4}/gm, ''); - } - - // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. - loose = next || /\n\n(?!\s*$)/.test(item); - if (i !== l - 1) { - next = item.charAt(item.length - 1) === '\n'; - if (!loose) { loose = next; } - } - - if (loose) { - list.loose = true; - } - - // Check for task list items - if (this.options.gfm) { - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; - if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); - } - } - - list.items.push({ - type: 'list_item', - raw: raw, - task: istask, - checked: ischecked, - loose: loose, - text: item - }); - } - - return list; - } - }; - - Tokenizer.prototype.html = function html (src) { - var cap = this.rules.block.html.exec(src); - if (cap) { - return { - type: this.options.sanitize - ? 'paragraph' - : 'html', - raw: cap[0], - pre: !this.options.sanitizer - && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$1(cap[0])) : cap[0] - }; - } - }; - - Tokenizer.prototype.def = function def (src) { - var cap = this.rules.block.def.exec(src); - if (cap) { - if (cap[3]) { cap[3] = cap[3].substring(1, cap[3].length - 1); } - var tag = cap[1].toLowerCase().replace(/\s+/g, ' '); - return { - tag: tag, - raw: cap[0], - href: cap[2], - title: cap[3] - }; - } - }; - - Tokenizer.prototype.table = function table (src) { - var cap = this.rules.block.table.exec(src); - if (cap) { - var item = { - type: 'table', - header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - item.raw = cap[0]; - - var l = item.align.length; - var i; - for (i = 0; i < l; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - l = item.cells.length; - for (i = 0; i < l; i++) { - item.cells[i] = splitCells$1( - item.cells[i].replace(/^ *\| *| *\| *$/g, ''), - item.header.length); - } - - return item; - } - } - }; - - Tokenizer.prototype.lheading = function lheading (src) { - var cap = this.rules.block.lheading.exec(src); - if (cap) { - return { - type: 'heading', - raw: cap[0], - depth: cap[2].charAt(0) === '=' ? 1 : 2, - text: cap[1] - }; - } - }; - - Tokenizer.prototype.paragraph = function paragraph (src) { - var cap = this.rules.block.paragraph.exec(src); - if (cap) { - return { - type: 'paragraph', - raw: cap[0], - text: cap[1].charAt(cap[1].length - 1) === '\n' - ? cap[1].slice(0, -1) - : cap[1] - }; - } - }; - - Tokenizer.prototype.text = function text (src, tokens) { - var cap = this.rules.block.text.exec(src); - if (cap) { - var lastToken = tokens[tokens.length - 1]; - if (lastToken && lastToken.type === 'text') { - return { - raw: cap[0], - text: cap[0] - }; - } - - return { - type: 'text', - raw: cap[0], - text: cap[0] - }; - } - }; - - Tokenizer.prototype.escape = function escape$1$1 (src) { - var cap = this.rules.inline.escape.exec(src); - if (cap) { - return { - type: 'escape', - raw: cap[0], - text: escape$1(cap[1]) - }; - } - }; - - Tokenizer.prototype.tag = function tag (src, inLink, inRawBlock) { - var cap = this.rules.inline.tag.exec(src); - if (cap) { - if (!inLink && /^/i.test(cap[0])) { - inLink = false; - } - if (!inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - inRawBlock = true; - } else if (inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - inRawBlock = false; - } - - return { - type: this.options.sanitize - ? 'text' - : 'html', - raw: cap[0], - inLink: inLink, - inRawBlock: inRawBlock, - text: this.options.sanitize - ? (this.options.sanitizer - ? this.options.sanitizer(cap[0]) - : escape$1(cap[0])) - : cap[0] - }; - } - }; - - Tokenizer.prototype.link = function link (src) { - var cap = this.rules.inline.link.exec(src); - if (cap) { - var trimmedUrl = cap[2].trim(); - if (!this.options.pedantic && /^$/.test(trimmedUrl))) { - return; - } - - // ending angle bracket cannot be escaped - var rtrimSlash = rtrim$1(trimmedUrl.slice(0, -1), '\\'); - if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { - return; - } - } else { - // find closing parenthesis - var lastParenIndex = findClosingBracket$1(cap[2], '()'); - if (lastParenIndex > -1) { - var start = cap[0].indexOf('!') === 0 ? 5 : 4; - var linkLen = start + cap[1].length + lastParenIndex; - cap[2] = cap[2].substring(0, lastParenIndex); - cap[0] = cap[0].substring(0, linkLen).trim(); - cap[3] = ''; - } - } - var href = cap[2]; - var title = ''; - if (this.options.pedantic) { - // split pedantic href and title - var link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); - - if (link) { - href = link[1]; - title = link[3]; - } - } else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - - href = href.trim(); - if (/^$/.test(trimmedUrl))) { - // pedantic allows starting angle bracket without ending angle bracket - href = href.slice(1); - } else { - href = href.slice(1, -1); - } - } - return outputLink(cap, { - href: href ? href.replace(this.rules.inline._escapes, '$1') : href, - title: title ? title.replace(this.rules.inline._escapes, '$1') : title - }, cap[0]); - } - }; - - Tokenizer.prototype.reflink = function reflink (src, links) { - var cap; - if ((cap = this.rules.inline.reflink.exec(src)) - || (cap = this.rules.inline.nolink.exec(src))) { - var link = (cap[2] || cap[1]).replace(/\s+/g, ' '); - link = links[link.toLowerCase()]; - if (!link || !link.href) { - var text = cap[0].charAt(0); - return { - type: 'text', - raw: text, - text: text - }; - } - return outputLink(cap, link, cap[0]); - } - }; - - Tokenizer.prototype.strong = function strong (src, maskedSrc, prevChar) { - if ( prevChar === void 0 ) prevChar = ''; - - var match = this.rules.inline.strong.start.exec(src); - - if (match && (!match[1] || (match[1] && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))))) { - maskedSrc = maskedSrc.slice(-1 * src.length); - var endReg = match[0] === '**' ? this.rules.inline.strong.endAst : this.rules.inline.strong.endUnd; - - endReg.lastIndex = 0; - - var cap; - while ((match = endReg.exec(maskedSrc)) != null) { - cap = this.rules.inline.strong.middle.exec(maskedSrc.slice(0, match.index + 3)); - if (cap) { - return { - type: 'strong', - raw: src.slice(0, cap[0].length), - text: src.slice(2, cap[0].length - 2) - }; - } - } - } - }; - - Tokenizer.prototype.em = function em (src, maskedSrc, prevChar) { - if ( prevChar === void 0 ) prevChar = ''; - - var match = this.rules.inline.em.start.exec(src); - - if (match && (!match[1] || (match[1] && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))))) { - maskedSrc = maskedSrc.slice(-1 * src.length); - var endReg = match[0] === '*' ? this.rules.inline.em.endAst : this.rules.inline.em.endUnd; - - endReg.lastIndex = 0; - - var cap; - while ((match = endReg.exec(maskedSrc)) != null) { - cap = this.rules.inline.em.middle.exec(maskedSrc.slice(0, match.index + 2)); - if (cap) { - return { - type: 'em', - raw: src.slice(0, cap[0].length), - text: src.slice(1, cap[0].length - 1) - }; - } - } - } - }; - - Tokenizer.prototype.codespan = function codespan (src) { - var cap = this.rules.inline.code.exec(src); - if (cap) { - var text = cap[2].replace(/\n/g, ' '); - var hasNonSpaceChars = /[^ ]/.test(text); - var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text); - if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) { - text = text.substring(1, text.length - 1); - } - text = escape$1(text, true); - return { - type: 'codespan', - raw: cap[0], - text: text - }; - } - }; - - Tokenizer.prototype.br = function br (src) { - var cap = this.rules.inline.br.exec(src); - if (cap) { - return { - type: 'br', - raw: cap[0] - }; - } - }; - - Tokenizer.prototype.del = function del (src) { - var cap = this.rules.inline.del.exec(src); - if (cap) { - return { - type: 'del', - raw: cap[0], - text: cap[2] - }; - } - }; - - Tokenizer.prototype.autolink = function autolink (src, mangle) { - var cap = this.rules.inline.autolink.exec(src); - if (cap) { - var text, href; - if (cap[2] === '@') { - text = escape$1(this.options.mangle ? mangle(cap[1]) : cap[1]); - href = 'mailto:' + text; - } else { - text = escape$1(cap[1]); - href = text; - } - - return { - type: 'link', - raw: cap[0], - text: text, - href: href, - tokens: [ - { - type: 'text', - raw: text, - text: text - } - ] - }; - } - }; - - Tokenizer.prototype.url = function url (src, mangle) { - var cap; - if (cap = this.rules.inline.url.exec(src)) { - var text, href; - if (cap[2] === '@') { - text = escape$1(this.options.mangle ? mangle(cap[0]) : cap[0]); - href = 'mailto:' + text; - } else { - // do extended autolink path validation - var prevCapZero; - do { - prevCapZero = cap[0]; - cap[0] = this.rules.inline._backpedal.exec(cap[0])[0]; - } while (prevCapZero !== cap[0]); - text = escape$1(cap[0]); - if (cap[1] === 'www.') { - href = 'http://' + text; - } else { - href = text; - } - } - return { - type: 'link', - raw: cap[0], - text: text, - href: href, - tokens: [ - { - type: 'text', - raw: text, - text: text - } - ] - }; - } - }; - - Tokenizer.prototype.inlineText = function inlineText (src, inRawBlock, smartypants) { - var cap = this.rules.inline.text.exec(src); - if (cap) { - var text; - if (inRawBlock) { - text = this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape$1(cap[0])) : cap[0]; - } else { - text = escape$1(this.options.smartypants ? smartypants(cap[0]) : cap[0]); - } - return { - type: 'text', - raw: cap[0], - text: text - }; - } - }; - - return Tokenizer; - }()); - - var noopTest$1 = helpers.noopTest; - var edit$1 = helpers.edit; - var merge$2 = helpers.merge; - - /** - * Block-Level Grammar - */ - var block = { - newline: /^(?: *(?:\n|$))+/, - code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/, - fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, - hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, - blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/, - html: '^ {0,3}(?:' // optional indentation - + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3) - + '|\\n*|$)' // (4) - + '|\\n*|$)' // (5) - + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) - + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag - + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag - + ')', - def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, - nptable: noopTest$1, - table: noopTest$1, - lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/, - // regex template, placeholders will be replaced according to different paragraph - // interruption rules of commonmark and the original markdown spec: - _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/, - text: /^[^\n]+/ - }; - - block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; - block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; - block.def = edit$1(block.def) - .replace('label', block._label) - .replace('title', block._title) - .getRegex(); - - block.bullet = /(?:[*+-]|\d{1,9}[.)])/; - block.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/; - block.item = edit$1(block.item, 'gm') - .replace(/bull/g, block.bullet) - .getRegex(); - - block.listItemStart = edit$1(/^( *)(bull)/) - .replace('bull', block.bullet) - .getRegex(); - - block.list = edit$1(block.list) - .replace(/bull/g, block.bullet) - .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') - .replace('def', '\\n+(?=' + block.def.source + ')') - .getRegex(); - - block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' - + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' - + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' - + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' - + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' - + '|track|ul'; - block._comment = /|$)/; - block.html = edit$1(block.html, 'i') - .replace('comment', block._comment) - .replace('tag', block._tag) - .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) - .getRegex(); - - block.paragraph = edit$1(block._paragraph) - .replace('hr', block.hr) - .replace('heading', ' {0,3}#{1,6} ') - .replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs - .replace('blockquote', ' {0,3}>') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)') - .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - - block.blockquote = edit$1(block.blockquote) - .replace('paragraph', block.paragraph) - .getRegex(); - - /** - * Normal Block Grammar - */ - - block.normal = merge$2({}, block); - - /** - * GFM Block Grammar - */ - - block.gfm = merge$2({}, block.normal, { - nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header - + ' {0,3}([-:]+ *\\|[-| :]*)' // Align - + '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)', // Cells - table: '^ *\\|(.+)\\n' // Header - + ' {0,3}\\|?( *[-:]+[-| :]*)' // Align - + '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells - }); - - block.gfm.nptable = edit$1(block.gfm.nptable) - .replace('hr', block.hr) - .replace('heading', ' {0,3}#{1,6} ') - .replace('blockquote', ' {0,3}>') - .replace('code', ' {4}[^\\n]') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)') - .replace('tag', block._tag) // tables can be interrupted by type (6) html blocks - .getRegex(); - - block.gfm.table = edit$1(block.gfm.table) - .replace('hr', block.hr) - .replace('heading', ' {0,3}#{1,6} ') - .replace('blockquote', ' {0,3}>') - .replace('code', ' {4}[^\\n]') - .replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n') - .replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', ')|<(?:script|pre|style|!--)') - .replace('tag', block._tag) // tables can be interrupted by type (6) html blocks - .getRegex(); - - /** - * Pedantic grammar (original John Gruber's loose markdown specification) - */ - - block.pedantic = merge$2({}, block.normal, { - html: edit$1( - '^ *(?:comment *(?:\\n|\\s*$)' - + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag - + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') - .replace('comment', block._comment) - .replace(/tag/g, '(?!(?:' - + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' - + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' - + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') - .getRegex(), - def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, - heading: /^(#{1,6})(.*)(?:\n+|$)/, - fences: noopTest$1, // fences not supported - paragraph: edit$1(block.normal._paragraph) - .replace('hr', block.hr) - .replace('heading', ' *#{1,6} *[^\n]') - .replace('lheading', block.lheading) - .replace('blockquote', ' {0,3}>') - .replace('|fences', '') - .replace('|list', '') - .replace('|html', '') - .getRegex() - }); - - /** - * Inline-Level Grammar - */ - var inline = { - escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, - autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, - url: noopTest$1, - tag: '^comment' - + '|^' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. - + '|^' // declaration, e.g. - + '|^', // CDATA section - link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/, - reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, - nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, - reflinkSearch: 'reflink|nolink(?!\\()', - strong: { - start: /^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/, // (1) returns if starts w/ punctuation - middle: /^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/, - endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline) - endUnd: /[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) - }, - em: { - start: /^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/, // (1) returns if starts w/ punctuation - middle: /^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/, - endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline) - endUnd: /[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline) - }, - code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, - br: /^( {2,}|\\)\n(?!\s*$)/, - del: noopTest$1, - text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~'; - inline.punctuation = edit$1(inline.punctuation).replace(/punctuation/g, inline._punctuation).getRegex(); - - // sequences em should skip over [title](link), `code`, - inline._blockSkip = '\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>'; - inline._overlapSkip = '__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*'; - - inline._comment = edit$1(block._comment).replace('(?:-->|$)', '-->').getRegex(); - - inline.em.start = edit$1(inline.em.start) - .replace(/punctuation/g, inline._punctuation) - .getRegex(); - - inline.em.middle = edit$1(inline.em.middle) - .replace(/punctuation/g, inline._punctuation) - .replace(/overlapSkip/g, inline._overlapSkip) - .getRegex(); - - inline.em.endAst = edit$1(inline.em.endAst, 'g') - .replace(/punctuation/g, inline._punctuation) - .getRegex(); - - inline.em.endUnd = edit$1(inline.em.endUnd, 'g') - .replace(/punctuation/g, inline._punctuation) - .getRegex(); - - inline.strong.start = edit$1(inline.strong.start) - .replace(/punctuation/g, inline._punctuation) - .getRegex(); - - inline.strong.middle = edit$1(inline.strong.middle) - .replace(/punctuation/g, inline._punctuation) - .replace(/overlapSkip/g, inline._overlapSkip) - .getRegex(); - - inline.strong.endAst = edit$1(inline.strong.endAst, 'g') - .replace(/punctuation/g, inline._punctuation) - .getRegex(); - - inline.strong.endUnd = edit$1(inline.strong.endUnd, 'g') - .replace(/punctuation/g, inline._punctuation) - .getRegex(); - - inline.blockSkip = edit$1(inline._blockSkip, 'g') - .getRegex(); - - inline.overlapSkip = edit$1(inline._overlapSkip, 'g') - .getRegex(); - - inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; - - inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; - inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; - inline.autolink = edit$1(inline.autolink) - .replace('scheme', inline._scheme) - .replace('email', inline._email) - .getRegex(); - - inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; - - inline.tag = edit$1(inline.tag) - .replace('comment', inline._comment) - .replace('attribute', inline._attribute) - .getRegex(); - - inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; - inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/; - inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; - - inline.link = edit$1(inline.link) - .replace('label', inline._label) - .replace('href', inline._href) - .replace('title', inline._title) - .getRegex(); - - inline.reflink = edit$1(inline.reflink) - .replace('label', inline._label) - .getRegex(); - - inline.reflinkSearch = edit$1(inline.reflinkSearch, 'g') - .replace('reflink', inline.reflink) - .replace('nolink', inline.nolink) - .getRegex(); - - /** - * Normal Inline Grammar - */ - - inline.normal = merge$2({}, inline); - - /** - * Pedantic Inline Grammar - */ - - inline.pedantic = merge$2({}, inline.normal, { - strong: { - start: /^__|\*\*/, - middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - endAst: /\*\*(?!\*)/g, - endUnd: /__(?!_)/g - }, - em: { - start: /^_|\*/, - middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/, - endAst: /\*(?!\*)/g, - endUnd: /_(?!_)/g - }, - link: edit$1(/^!?\[(label)\]\((.*?)\)/) - .replace('label', inline._label) - .getRegex(), - reflink: edit$1(/^!?\[(label)\]\s*\[([^\]]*)\]/) - .replace('label', inline._label) - .getRegex() - }); - - /** - * GFM Inline Grammar - */ - - inline.gfm = merge$2({}, inline.normal, { - escape: edit$1(inline.escape).replace('])', '~|])').getRegex(), - _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, - url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, - _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, - del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/, - text: /^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\ 0.5) { - ch = 'x' + ch.toString(16); - } - out += '&#' + ch + ';'; - } - - return out; - } - - /** - * Block Lexer - */ - var Lexer = /*@__PURE__*/(function () { - function Lexer(options) { - this.tokens = []; - this.tokens.links = Object.create(null); - this.options = options || defaults$2; - this.options.tokenizer = this.options.tokenizer || new Tokenizer(); - this.tokenizer = this.options.tokenizer; - this.tokenizer.options = this.options; - - var rules = { - block: block$1.normal, - inline: inline$1.normal - }; - - if (this.options.pedantic) { - rules.block = block$1.pedantic; - rules.inline = inline$1.pedantic; - } else if (this.options.gfm) { - rules.block = block$1.gfm; - if (this.options.breaks) { - rules.inline = inline$1.breaks; - } else { - rules.inline = inline$1.gfm; - } - } - this.tokenizer.rules = rules; - } - - var staticAccessors = { rules: { configurable: true } }; - - /** - * Expose Rules - */ - staticAccessors.rules.get = function () { - return { - block: block$1, - inline: inline$1 - }; - }; - - /** - * Static Lex Method - */ - Lexer.lex = function lex (src, options) { - var lexer = new Lexer(options); - return lexer.lex(src); - }; - - /** - * Static Lex Inline Method - */ - Lexer.lexInline = function lexInline (src, options) { - var lexer = new Lexer(options); - return lexer.inlineTokens(src); - }; - - /** - * Preprocessing - */ - Lexer.prototype.lex = function lex (src) { - src = src - .replace(/\r\n|\r/g, '\n') - .replace(/\t/g, ' '); - - this.blockTokens(src, this.tokens, true); - - this.inline(this.tokens); - - return this.tokens; - }; - - /** - * Lexing - */ - Lexer.prototype.blockTokens = function blockTokens (src, tokens, top) { - if ( tokens === void 0 ) tokens = []; - if ( top === void 0 ) top = true; - - if (this.options.pedantic) { - src = src.replace(/^ +$/gm, ''); - } - var token, i, l, lastToken; - - while (src) { - // newline - if (token = this.tokenizer.space(src)) { - src = src.substring(token.raw.length); - if (token.type) { - tokens.push(token); - } - continue; - } - - // code - if (token = this.tokenizer.code(src, tokens)) { - src = src.substring(token.raw.length); - if (token.type) { - tokens.push(token); - } else { - lastToken = tokens[tokens.length - 1]; - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - } - continue; - } - - // fences - if (token = this.tokenizer.fences(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // heading - if (token = this.tokenizer.heading(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // table no leading pipe (gfm) - if (token = this.tokenizer.nptable(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // hr - if (token = this.tokenizer.hr(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // blockquote - if (token = this.tokenizer.blockquote(src)) { - src = src.substring(token.raw.length); - token.tokens = this.blockTokens(token.text, [], top); - tokens.push(token); - continue; - } - - // list - if (token = this.tokenizer.list(src)) { - src = src.substring(token.raw.length); - l = token.items.length; - for (i = 0; i < l; i++) { - token.items[i].tokens = this.blockTokens(token.items[i].text, [], false); - } - tokens.push(token); - continue; - } - - // html - if (token = this.tokenizer.html(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // def - if (top && (token = this.tokenizer.def(src))) { - src = src.substring(token.raw.length); - if (!this.tokens.links[token.tag]) { - this.tokens.links[token.tag] = { - href: token.href, - title: token.title - }; - } - continue; - } - - // table (gfm) - if (token = this.tokenizer.table(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // lheading - if (token = this.tokenizer.lheading(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // top-level paragraph - if (top && (token = this.tokenizer.paragraph(src))) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // text - if (token = this.tokenizer.text(src, tokens)) { - src = src.substring(token.raw.length); - if (token.type) { - tokens.push(token); - } else { - lastToken = tokens[tokens.length - 1]; - lastToken.raw += '\n' + token.raw; - lastToken.text += '\n' + token.text; - } - continue; - } - - if (src) { - var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); - if (this.options.silent) { - console.error(errMsg); - break; - } else { - throw new Error(errMsg); - } - } - } - - return tokens; - }; - - Lexer.prototype.inline = function inline (tokens) { - var i, - j, - k, - l2, - row, - token; - - var l = tokens.length; - for (i = 0; i < l; i++) { - token = tokens[i]; - switch (token.type) { - case 'paragraph': - case 'text': - case 'heading': { - token.tokens = []; - this.inlineTokens(token.text, token.tokens); - break; - } - case 'table': { - token.tokens = { - header: [], - cells: [] - }; - - // header - l2 = token.header.length; - for (j = 0; j < l2; j++) { - token.tokens.header[j] = []; - this.inlineTokens(token.header[j], token.tokens.header[j]); - } - - // cells - l2 = token.cells.length; - for (j = 0; j < l2; j++) { - row = token.cells[j]; - token.tokens.cells[j] = []; - for (k = 0; k < row.length; k++) { - token.tokens.cells[j][k] = []; - this.inlineTokens(row[k], token.tokens.cells[j][k]); - } - } - - break; - } - case 'blockquote': { - this.inline(token.tokens); - break; - } - case 'list': { - l2 = token.items.length; - for (j = 0; j < l2; j++) { - this.inline(token.items[j].tokens); - } - break; - } - } - } - - return tokens; - }; - - /** - * Lexing/Compiling - */ - Lexer.prototype.inlineTokens = function inlineTokens (src, tokens, inLink, inRawBlock) { - if ( tokens === void 0 ) tokens = []; - if ( inLink === void 0 ) inLink = false; - if ( inRawBlock === void 0 ) inRawBlock = false; - - var token; - - // String with links masked to avoid interference with em and strong - var maskedSrc = src; - var match; - var keepPrevChar, prevChar; - - // Mask out reflinks - if (this.tokens.links) { - var links = Object.keys(this.tokens.links); - if (links.length > 0) { - while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) { - if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) { - maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString$1('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex); - } - } - } - } - // Mask out other blocks - while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) { - maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString$1('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex); - } - - while (src) { - if (!keepPrevChar) { - prevChar = ''; - } - keepPrevChar = false; - // escape - if (token = this.tokenizer.escape(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // tag - if (token = this.tokenizer.tag(src, inLink, inRawBlock)) { - src = src.substring(token.raw.length); - inLink = token.inLink; - inRawBlock = token.inRawBlock; - tokens.push(token); - continue; - } - - // link - if (token = this.tokenizer.link(src)) { - src = src.substring(token.raw.length); - if (token.type === 'link') { - token.tokens = this.inlineTokens(token.text, [], true, inRawBlock); - } - tokens.push(token); - continue; - } - - // reflink, nolink - if (token = this.tokenizer.reflink(src, this.tokens.links)) { - src = src.substring(token.raw.length); - if (token.type === 'link') { - token.tokens = this.inlineTokens(token.text, [], true, inRawBlock); - } - tokens.push(token); - continue; - } - - // strong - if (token = this.tokenizer.strong(src, maskedSrc, prevChar)) { - src = src.substring(token.raw.length); - token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock); - tokens.push(token); - continue; - } - - // em - if (token = this.tokenizer.em(src, maskedSrc, prevChar)) { - src = src.substring(token.raw.length); - token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock); - tokens.push(token); - continue; - } - - // code - if (token = this.tokenizer.codespan(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // br - if (token = this.tokenizer.br(src)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // del (gfm) - if (token = this.tokenizer.del(src)) { - src = src.substring(token.raw.length); - token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock); - tokens.push(token); - continue; - } - - // autolink - if (token = this.tokenizer.autolink(src, mangle)) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // url (gfm) - if (!inLink && (token = this.tokenizer.url(src, mangle))) { - src = src.substring(token.raw.length); - tokens.push(token); - continue; - } - - // text - if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) { - src = src.substring(token.raw.length); - prevChar = token.raw.slice(-1); - keepPrevChar = true; - tokens.push(token); - continue; - } - - if (src) { - var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0); - if (this.options.silent) { - console.error(errMsg); - break; - } else { - throw new Error(errMsg); - } - } - } - - return tokens; - }; - - Object.defineProperties( Lexer, staticAccessors ); - - return Lexer; - }()); - - var defaults$3 = defaults.defaults; - - var cleanUrl$1 = helpers.cleanUrl; - var escape$2 = helpers.escape; - - /** - * Renderer - */ - var Renderer = /*@__PURE__*/(function () { - function Renderer(options) { - this.options = options || defaults$3; - } - - Renderer.prototype.code = function code (code$1, infostring, escaped) { - var lang = (infostring || '').match(/\S*/)[0]; - if (this.options.highlight) { - var out = this.options.highlight(code$1, lang); - if (out != null && out !== code$1) { - escaped = true; - code$1 = out; - } - } - - code$1 = code$1.replace(/\n$/, '') + '\n'; - - if (!lang) { - return '
'
-          + (escaped ? code$1 : escape$2(code$1, true))
-          + '
\n'; - } - - return '
'
-        + (escaped ? code$1 : escape$2(code$1, true))
-        + '
\n'; - }; - - Renderer.prototype.blockquote = function blockquote (quote) { - return '
\n' + quote + '
\n'; - }; - - Renderer.prototype.html = function html (html$1) { - return html$1; - }; - - Renderer.prototype.heading = function heading (text, level, raw, slugger) { - if (this.options.headerIds) { - return '' - + text - + '\n'; - } - // ignore IDs - return '' + text + '\n'; - }; - - Renderer.prototype.hr = function hr () { - return this.options.xhtml ? '
\n' : '
\n'; - }; - - Renderer.prototype.list = function list (body, ordered, start) { - var type = ordered ? 'ol' : 'ul', - startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; - return '<' + type + startatt + '>\n' + body + '\n'; - }; - - Renderer.prototype.listitem = function listitem (text) { - return '
  • ' + text + '
  • \n'; - }; - - Renderer.prototype.checkbox = function checkbox (checked) { - return ' '; - }; - - Renderer.prototype.paragraph = function paragraph (text) { - return '

    ' + text + '

    \n'; - }; - - Renderer.prototype.table = function table (header, body) { - if (body) { body = '' + body + ''; } - - return '\n' - + '\n' - + header - + '\n' - + body - + '
    \n'; - }; - - Renderer.prototype.tablerow = function tablerow (content) { - return '\n' + content + '\n'; - }; - - Renderer.prototype.tablecell = function tablecell (content, flags) { - var type = flags.header ? 'th' : 'td'; - var tag = flags.align - ? '<' + type + ' align="' + flags.align + '">' - : '<' + type + '>'; - return tag + content + '\n'; - }; - - // span level renderer - Renderer.prototype.strong = function strong (text) { - return '' + text + ''; - }; - - Renderer.prototype.em = function em (text) { - return '' + text + ''; - }; - - Renderer.prototype.codespan = function codespan (text) { - return '' + text + ''; - }; - - Renderer.prototype.br = function br () { - return this.options.xhtml ? '
    ' : '
    '; - }; - - Renderer.prototype.del = function del (text) { - return '' + text + ''; - }; - - Renderer.prototype.link = function link (href, title, text) { - href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href); - if (href === null) { - return text; - } - var out = '
    '; - return out; - }; - - Renderer.prototype.image = function image (href, title, text) { - href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href); - if (href === null) { - return text; - } - - var out = '' + text + '' : '>'; - return out; - }; - - Renderer.prototype.text = function text (text$1) { - return text$1; - }; - - return Renderer; - }()); - - /** - * TextRenderer - * returns only the textual part of the token - */ - var TextRenderer = /*@__PURE__*/(function () { - function TextRenderer () {} - - TextRenderer.prototype.strong = function strong (text) { - return text; - }; - - TextRenderer.prototype.em = function em (text) { - return text; - }; - - TextRenderer.prototype.codespan = function codespan (text) { - return text; - }; - - TextRenderer.prototype.del = function del (text) { - return text; - }; - - TextRenderer.prototype.html = function html (text) { - return text; - }; - - TextRenderer.prototype.text = function text (text$1) { - return text$1; - }; - - TextRenderer.prototype.link = function link (href, title, text) { - return '' + text; - }; - - TextRenderer.prototype.image = function image (href, title, text) { - return '' + text; - }; - - TextRenderer.prototype.br = function br () { - return ''; - }; - - return TextRenderer; - }()); - - /** - * Slugger generates header id - */ - var Slugger = /*@__PURE__*/(function () { - function Slugger() { - this.seen = {}; - } - - Slugger.prototype.serialize = function serialize (value) { - return value - .toLowerCase() - .trim() - // remove html tags - .replace(/<[!\/a-z].*?>/ig, '') - // remove unwanted chars - .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '') - .replace(/\s/g, '-'); - }; - - /** - * Finds the next safe (unique) slug to use - */ - Slugger.prototype.getNextSafeSlug = function getNextSafeSlug (originalSlug, isDryRun) { - var slug = originalSlug; - var occurenceAccumulator = 0; - if (this.seen.hasOwnProperty(slug)) { - occurenceAccumulator = this.seen[originalSlug]; - do { - occurenceAccumulator++; - slug = originalSlug + '-' + occurenceAccumulator; - } while (this.seen.hasOwnProperty(slug)); - } - if (!isDryRun) { - this.seen[originalSlug] = occurenceAccumulator; - this.seen[slug] = 0; - } - return slug; - }; - - /** - * Convert string to unique id - * @param {object} options - * @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator. - */ - Slugger.prototype.slug = function slug (value, options) { - if ( options === void 0 ) options = {}; - - var slug = this.serialize(value); - return this.getNextSafeSlug(slug, options.dryrun); - }; - - return Slugger; - }()); - - var defaults$4 = defaults.defaults; - - var unescape$1 = helpers.unescape; - - /** - * Parsing & Compiling - */ - var Parser = /*@__PURE__*/(function () { - function Parser(options) { - this.options = options || defaults$4; - this.options.renderer = this.options.renderer || new Renderer(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; - this.textRenderer = new TextRenderer(); - this.slugger = new Slugger(); - } - - /** - * Static Parse Method - */ - Parser.parse = function parse (tokens, options) { - var parser = new Parser(options); - return parser.parse(tokens); - }; - - /** - * Static Parse Inline Method - */ - Parser.parseInline = function parseInline (tokens, options) { - var parser = new Parser(options); - return parser.parseInline(tokens); - }; - - /** - * Parse Loop - */ - Parser.prototype.parse = function parse (tokens, top) { - if ( top === void 0 ) top = true; - - var out = '', - i, - j, - k, - l2, - l3, - row, - cell, - header, - body, - token, - ordered, - start, - loose, - itemBody, - item, - checked, - task, - checkbox; - - var l = tokens.length; - for (i = 0; i < l; i++) { - token = tokens[i]; - switch (token.type) { - case 'space': { - continue; - } - case 'hr': { - out += this.renderer.hr(); - continue; - } - case 'heading': { - out += this.renderer.heading( - this.parseInline(token.tokens), - token.depth, - unescape$1(this.parseInline(token.tokens, this.textRenderer)), - this.slugger); - continue; - } - case 'code': { - out += this.renderer.code(token.text, - token.lang, - token.escaped); - continue; - } - case 'table': { - header = ''; - - // header - cell = ''; - l2 = token.header.length; - for (j = 0; j < l2; j++) { - cell += this.renderer.tablecell( - this.parseInline(token.tokens.header[j]), - { header: true, align: token.align[j] } - ); - } - header += this.renderer.tablerow(cell); - - body = ''; - l2 = token.cells.length; - for (j = 0; j < l2; j++) { - row = token.tokens.cells[j]; - - cell = ''; - l3 = row.length; - for (k = 0; k < l3; k++) { - cell += this.renderer.tablecell( - this.parseInline(row[k]), - { header: false, align: token.align[k] } - ); - } - - body += this.renderer.tablerow(cell); - } - out += this.renderer.table(header, body); - continue; - } - case 'blockquote': { - body = this.parse(token.tokens); - out += this.renderer.blockquote(body); - continue; - } - case 'list': { - ordered = token.ordered; - start = token.start; - loose = token.loose; - l2 = token.items.length; - - body = ''; - for (j = 0; j < l2; j++) { - item = token.items[j]; - checked = item.checked; - task = item.task; - - itemBody = ''; - if (item.task) { - checkbox = this.renderer.checkbox(checked); - if (loose) { - if (item.tokens.length > 0 && item.tokens[0].type === 'text') { - item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; - if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { - item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text; - } - } else { - item.tokens.unshift({ - type: 'text', - text: checkbox - }); - } - } else { - itemBody += checkbox; - } - } - - itemBody += this.parse(item.tokens, loose); - body += this.renderer.listitem(itemBody, task, checked); - } - - out += this.renderer.list(body, ordered, start); - continue; - } - case 'html': { - // TODO parse inline content if parameter markdown=1 - out += this.renderer.html(token.text); - continue; - } - case 'paragraph': { - out += this.renderer.paragraph(this.parseInline(token.tokens)); - continue; - } - case 'text': { - body = token.tokens ? this.parseInline(token.tokens) : token.text; - while (i + 1 < l && tokens[i + 1].type === 'text') { - token = tokens[++i]; - body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text); - } - out += top ? this.renderer.paragraph(body) : body; - continue; - } - default: { - var errMsg = 'Token with "' + token.type + '" type was not found.'; - if (this.options.silent) { - console.error(errMsg); - return; - } else { - throw new Error(errMsg); - } - } - } - } - - return out; - }; - - /** - * Parse Inline Tokens - */ - Parser.prototype.parseInline = function parseInline (tokens, renderer) { - renderer = renderer || this.renderer; - var out = '', - i, - token; - - var l = tokens.length; - for (i = 0; i < l; i++) { - token = tokens[i]; - switch (token.type) { - case 'escape': { - out += renderer.text(token.text); - break; - } - case 'html': { - out += renderer.html(token.text); - break; - } - case 'link': { - out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer)); - break; - } - case 'image': { - out += renderer.image(token.href, token.title, token.text); - break; - } - case 'strong': { - out += renderer.strong(this.parseInline(token.tokens, renderer)); - break; - } - case 'em': { - out += renderer.em(this.parseInline(token.tokens, renderer)); - break; - } - case 'codespan': { - out += renderer.codespan(token.text); - break; - } - case 'br': { - out += renderer.br(); - break; - } - case 'del': { - out += renderer.del(this.parseInline(token.tokens, renderer)); - break; - } - case 'text': { - out += renderer.text(token.text); - break; - } - default: { - var errMsg = 'Token with "' + token.type + '" type was not found.'; - if (this.options.silent) { - console.error(errMsg); - return; - } else { - throw new Error(errMsg); - } - } - } - } - return out; - }; - - return Parser; - }()); - - var merge$3 = helpers.merge; - var checkSanitizeDeprecation$1 = helpers.checkSanitizeDeprecation; - var escape$3 = helpers.escape; - - var getDefaults = defaults.getDefaults; - var changeDefaults = defaults.changeDefaults; - var defaults$5 = defaults.defaults; - - /** - * Marked - */ - function marked(src, opt, callback) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked(): input parameter is undefined or null'); - } - if (typeof src !== 'string') { - throw new Error('marked(): input parameter is of type ' - + Object.prototype.toString.call(src) + ', string expected'); - } - - if (typeof opt === 'function') { - callback = opt; - opt = null; - } - - opt = merge$3({}, marked.defaults, opt || {}); - checkSanitizeDeprecation$1(opt); - - if (callback) { - var highlight = opt.highlight; - var tokens; - - try { - tokens = Lexer.lex(src, opt); - } catch (e) { - return callback(e); - } - - var done = function(err) { - var out; - - if (!err) { - try { - out = Parser.parse(tokens, opt); - } catch (e) { - err = e; - } - } - - opt.highlight = highlight; - - return err - ? callback(err) - : callback(null, out); - }; - - if (!highlight || highlight.length < 3) { - return done(); - } - - delete opt.highlight; - - if (!tokens.length) { return done(); } - - var pending = 0; - marked.walkTokens(tokens, function(token) { - if (token.type === 'code') { - pending++; - setTimeout(function () { - highlight(token.text, token.lang, function(err, code) { - if (err) { - return done(err); - } - if (code != null && code !== token.text) { - token.text = code; - token.escaped = true; - } - - pending--; - if (pending === 0) { - done(); - } - }); - }, 0); - } - }); - - if (pending === 0) { - done(); - } - - return; - } - - try { - var tokens$1 = Lexer.lex(src, opt); - if (opt.walkTokens) { - marked.walkTokens(tokens$1, opt.walkTokens); - } - return Parser.parse(tokens$1, opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - if (opt.silent) { - return '

    An error occurred:

    '
    -          + escape$3(e.message + '', true)
    -          + '
    '; - } - throw e; - } - } - - /** - * Options - */ - - marked.options = - marked.setOptions = function(opt) { - merge$3(marked.defaults, opt); - changeDefaults(marked.defaults); - return marked; - }; - - marked.getDefaults = getDefaults; - - marked.defaults = defaults$5; - - /** - * Use Extension - */ - - marked.use = function(extension) { - var opts = merge$3({}, extension); - if (extension.renderer) { - var renderer = marked.defaults.renderer || new Renderer(); - var loop = function ( prop ) { - var prevRenderer = renderer[prop]; - renderer[prop] = function () { - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - var ret = extension.renderer[prop].apply(renderer, args); - if (ret === false) { - ret = prevRenderer.apply(renderer, args); - } - return ret; - }; - }; - - for (var prop in extension.renderer) loop( prop ); - opts.renderer = renderer; - } - if (extension.tokenizer) { - var tokenizer = marked.defaults.tokenizer || new Tokenizer(); - var loop$1 = function ( prop ) { - var prevTokenizer = tokenizer[prop$1]; - tokenizer[prop$1] = function () { - var args = [], len = arguments.length; - while ( len-- ) args[ len ] = arguments[ len ]; - - var ret = extension.tokenizer[prop$1].apply(tokenizer, args); - if (ret === false) { - ret = prevTokenizer.apply(tokenizer, args); - } - return ret; - }; - }; - - for (var prop$1 in extension.tokenizer) loop$1( prop ); - opts.tokenizer = tokenizer; - } - if (extension.walkTokens) { - var walkTokens = marked.defaults.walkTokens; - opts.walkTokens = function (token) { - extension.walkTokens(token); - if (walkTokens) { - walkTokens(token); - } - }; - } - marked.setOptions(opts); - }; - - /** - * Run callback for every token - */ - - marked.walkTokens = function(tokens, callback) { - for (var i$3 = 0, list$3 = tokens; i$3 < list$3.length; i$3 += 1) { - var token = list$3[i$3]; - - callback(token); - switch (token.type) { - case 'table': { - for (var i = 0, list = token.tokens.header; i < list.length; i += 1) { - var cell = list[i]; - - marked.walkTokens(cell, callback); - } - for (var i$2 = 0, list$2 = token.tokens.cells; i$2 < list$2.length; i$2 += 1) { - var row = list$2[i$2]; - - for (var i$1 = 0, list$1 = row; i$1 < list$1.length; i$1 += 1) { - var cell$1 = list$1[i$1]; - - marked.walkTokens(cell$1, callback); - } - } - break; - } - case 'list': { - marked.walkTokens(token.items, callback); - break; - } - default: { - if (token.tokens) { - marked.walkTokens(token.tokens, callback); - } - } - } - } - }; - - /** - * Parse Inline - */ - marked.parseInline = function(src, opt) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked.parseInline(): input parameter is undefined or null'); - } - if (typeof src !== 'string') { - throw new Error('marked.parseInline(): input parameter is of type ' - + Object.prototype.toString.call(src) + ', string expected'); - } - - opt = merge$3({}, marked.defaults, opt || {}); - checkSanitizeDeprecation$1(opt); - - try { - var tokens = Lexer.lexInline(src, opt); - if (opt.walkTokens) { - marked.walkTokens(tokens, opt.walkTokens); - } - return Parser.parseInline(tokens, opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - if (opt.silent) { - return '

    An error occurred:

    '
    -          + escape$3(e.message + '', true)
    -          + '
    '; - } - throw e; - } - }; - - /** - * Expose - */ - - marked.Parser = Parser; - marked.parser = Parser.parse; - - marked.Renderer = Renderer; - marked.TextRenderer = TextRenderer; - - marked.Lexer = Lexer; - marked.lexer = Lexer.lex; - - marked.Tokenizer = Tokenizer; - - marked.Slugger = Slugger; - - marked.parse = marked; - - var marked_1 = marked; - - /** - * Render github corner - * @param {Object} data URL for the View Source on Github link - * @param {String} cornerExternalLinkTarge value of the target attribute of the link - * @return {String} SVG element as string - */ - function corner(data, cornerExternalLinkTarge) { - if (!data) { - return ''; - } - - if (!/\/\//.test(data)) { - data = 'https://github.com/' + data; - } - - data = data.replace(/^git\+/, ''); - // Double check - cornerExternalLinkTarge = cornerExternalLinkTarge || '_blank'; - - return ( - "
    " + - '' + - '' - ); - } - - /** - * Renders main content - * @param {Object} config Configuration object - * @returns {String} HTML of the main content - */ - function main(config) { - var name = config.name ? config.name : ''; - - var aside = - '' + - ''; - return ( - "
    " + aside + - '
    ' + - '
    ' + - '
    ' + - '
    ' - ); - } - - /** - * Cover Page - * @returns {String} Cover page - */ - function cover() { - var SL = ', 100%, 85%'; - var bgc = - 'linear-gradient(to left bottom, ' + - "hsl(" + (Math.floor(Math.random() * 255) + SL) + ") 0%," + - "hsl(" + (Math.floor(Math.random() * 255) + SL) + ") 100%)"; - - return ( - "
    " + - '
    ' + - '
    ' + - '
    ' - ); - } - - /** - * Render tree - * @param {Array} toc Array of TOC section links - * @param {String} tpl TPL list - * @return {String} Rendered tree - */ - function tree(toc, tpl) { - if ( tpl === void 0 ) tpl = '
      {inner}
    '; - - if (!toc || !toc.length) { - return ''; - } - - var innerHTML = ''; - toc.forEach(function (node) { - var title = node.title.replace(/(<([^>]+)>)/g, ''); - innerHTML += "
  • " + (node.title) + "
  • "; - if (node.children) { - innerHTML += tree(node.children, tpl); - } - }); - return tpl.replace('{inner}', innerHTML); - } - - function helper(className, content) { - return ("

    " + (content.slice(5).trim()) + "

    "); - } - - function theme(color) { - return (""); - } - - /** - * Gen toc tree - * @link https://github.com/killercup/grock/blob/5280ae63e16c5739e9233d9009bc235ed7d79a50/styles/solarized/assets/js/behavior.coffee#L54-L81 - * @param {Array} toc List of TOC elements - * @param {Number} maxLevel Deep level - * @return {Array} Headlines - */ - function genTree(toc, maxLevel) { - var headlines = []; - var last = {}; - - toc.forEach(function (headline) { - var level = headline.level || 1; - var len = level - 1; - - if (level > maxLevel) { - return; - } - - if (last[len]) { - last[len].children = (last[len].children || []).concat(headline); - } else { - headlines.push(headline); - } - - last[level] = headline; - }); - - return headlines; - } - - var cache$1 = {}; - var re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g; - - function lower(string) { - return string.toLowerCase(); - } - - function slugify(str) { - if (typeof str !== 'string') { - return ''; - } - - var slug = str - .trim() - .replace(/[A-Z]+/g, lower) - .replace(/<[^>]+>/g, '') - .replace(re, '') - .replace(/\s/g, '-') - .replace(/-+/g, '-') - .replace(/^(\d)/, '_$1'); - var count = cache$1[slug]; - - count = hasOwn.call(cache$1, slug) ? count + 1 : 0; - cache$1[slug] = count; - - if (count) { - slug = slug + '-' + count; - } - - return slug; - } - - slugify.clear = function () { - cache$1 = {}; - }; - - /* eslint-disable */ - - // ============================================================================= - // DO NOT EDIT: This file is auto-generated by an /build/emoji.js - // ============================================================================= - - var emojiData = { - "baseURL": "https://github.githubassets.com/images/icons/emoji/", - "data": { - "100": "unicode/1f4af.png?v8", - "1234": "unicode/1f522.png?v8", - "+1": "unicode/1f44d.png?v8", - "-1": "unicode/1f44e.png?v8", - "1st_place_medal": "unicode/1f947.png?v8", - "2nd_place_medal": "unicode/1f948.png?v8", - "3rd_place_medal": "unicode/1f949.png?v8", - "8ball": "unicode/1f3b1.png?v8", - "a": "unicode/1f170.png?v8", - "ab": "unicode/1f18e.png?v8", - "abacus": "unicode/1f9ee.png?v8", - "abc": "unicode/1f524.png?v8", - "abcd": "unicode/1f521.png?v8", - "accept": "unicode/1f251.png?v8", - "accordion": "unicode/1fa97.png?v8", - "adhesive_bandage": "unicode/1fa79.png?v8", - "adult": "unicode/1f9d1.png?v8", - "aerial_tramway": "unicode/1f6a1.png?v8", - "afghanistan": "unicode/1f1e6-1f1eb.png?v8", - "airplane": "unicode/2708.png?v8", - "aland_islands": "unicode/1f1e6-1f1fd.png?v8", - "alarm_clock": "unicode/23f0.png?v8", - "albania": "unicode/1f1e6-1f1f1.png?v8", - "alembic": "unicode/2697.png?v8", - "algeria": "unicode/1f1e9-1f1ff.png?v8", - "alien": "unicode/1f47d.png?v8", - "ambulance": "unicode/1f691.png?v8", - "american_samoa": "unicode/1f1e6-1f1f8.png?v8", - "amphora": "unicode/1f3fa.png?v8", - "anatomical_heart": "unicode/1fac0.png?v8", - "anchor": "unicode/2693.png?v8", - "andorra": "unicode/1f1e6-1f1e9.png?v8", - "angel": "unicode/1f47c.png?v8", - "anger": "unicode/1f4a2.png?v8", - "angola": "unicode/1f1e6-1f1f4.png?v8", - "angry": "unicode/1f620.png?v8", - "anguilla": "unicode/1f1e6-1f1ee.png?v8", - "anguished": "unicode/1f627.png?v8", - "ant": "unicode/1f41c.png?v8", - "antarctica": "unicode/1f1e6-1f1f6.png?v8", - "antigua_barbuda": "unicode/1f1e6-1f1ec.png?v8", - "apple": "unicode/1f34e.png?v8", - "aquarius": "unicode/2652.png?v8", - "argentina": "unicode/1f1e6-1f1f7.png?v8", - "aries": "unicode/2648.png?v8", - "armenia": "unicode/1f1e6-1f1f2.png?v8", - "arrow_backward": "unicode/25c0.png?v8", - "arrow_double_down": "unicode/23ec.png?v8", - "arrow_double_up": "unicode/23eb.png?v8", - "arrow_down": "unicode/2b07.png?v8", - "arrow_down_small": "unicode/1f53d.png?v8", - "arrow_forward": "unicode/25b6.png?v8", - "arrow_heading_down": "unicode/2935.png?v8", - "arrow_heading_up": "unicode/2934.png?v8", - "arrow_left": "unicode/2b05.png?v8", - "arrow_lower_left": "unicode/2199.png?v8", - "arrow_lower_right": "unicode/2198.png?v8", - "arrow_right": "unicode/27a1.png?v8", - "arrow_right_hook": "unicode/21aa.png?v8", - "arrow_up": "unicode/2b06.png?v8", - "arrow_up_down": "unicode/2195.png?v8", - "arrow_up_small": "unicode/1f53c.png?v8", - "arrow_upper_left": "unicode/2196.png?v8", - "arrow_upper_right": "unicode/2197.png?v8", - "arrows_clockwise": "unicode/1f503.png?v8", - "arrows_counterclockwise": "unicode/1f504.png?v8", - "art": "unicode/1f3a8.png?v8", - "articulated_lorry": "unicode/1f69b.png?v8", - "artificial_satellite": "unicode/1f6f0.png?v8", - "artist": "unicode/1f9d1-1f3a8.png?v8", - "aruba": "unicode/1f1e6-1f1fc.png?v8", - "ascension_island": "unicode/1f1e6-1f1e8.png?v8", - "asterisk": "unicode/002a-20e3.png?v8", - "astonished": "unicode/1f632.png?v8", - "astronaut": "unicode/1f9d1-1f680.png?v8", - "athletic_shoe": "unicode/1f45f.png?v8", - "atm": "unicode/1f3e7.png?v8", - "atom": "atom.png?v8", - "atom_symbol": "unicode/269b.png?v8", - "australia": "unicode/1f1e6-1f1fa.png?v8", - "austria": "unicode/1f1e6-1f1f9.png?v8", - "auto_rickshaw": "unicode/1f6fa.png?v8", - "avocado": "unicode/1f951.png?v8", - "axe": "unicode/1fa93.png?v8", - "azerbaijan": "unicode/1f1e6-1f1ff.png?v8", - "b": "unicode/1f171.png?v8", - "baby": "unicode/1f476.png?v8", - "baby_bottle": "unicode/1f37c.png?v8", - "baby_chick": "unicode/1f424.png?v8", - "baby_symbol": "unicode/1f6bc.png?v8", - "back": "unicode/1f519.png?v8", - "bacon": "unicode/1f953.png?v8", - "badger": "unicode/1f9a1.png?v8", - "badminton": "unicode/1f3f8.png?v8", - "bagel": "unicode/1f96f.png?v8", - "baggage_claim": "unicode/1f6c4.png?v8", - "baguette_bread": "unicode/1f956.png?v8", - "bahamas": "unicode/1f1e7-1f1f8.png?v8", - "bahrain": "unicode/1f1e7-1f1ed.png?v8", - "balance_scale": "unicode/2696.png?v8", - "bald_man": "unicode/1f468-1f9b2.png?v8", - "bald_woman": "unicode/1f469-1f9b2.png?v8", - "ballet_shoes": "unicode/1fa70.png?v8", - "balloon": "unicode/1f388.png?v8", - "ballot_box": "unicode/1f5f3.png?v8", - "ballot_box_with_check": "unicode/2611.png?v8", - "bamboo": "unicode/1f38d.png?v8", - "banana": "unicode/1f34c.png?v8", - "bangbang": "unicode/203c.png?v8", - "bangladesh": "unicode/1f1e7-1f1e9.png?v8", - "banjo": "unicode/1fa95.png?v8", - "bank": "unicode/1f3e6.png?v8", - "bar_chart": "unicode/1f4ca.png?v8", - "barbados": "unicode/1f1e7-1f1e7.png?v8", - "barber": "unicode/1f488.png?v8", - "baseball": "unicode/26be.png?v8", - "basecamp": "basecamp.png?v8", - "basecampy": "basecampy.png?v8", - "basket": "unicode/1f9fa.png?v8", - "basketball": "unicode/1f3c0.png?v8", - "basketball_man": "unicode/26f9-2642.png?v8", - "basketball_woman": "unicode/26f9-2640.png?v8", - "bat": "unicode/1f987.png?v8", - "bath": "unicode/1f6c0.png?v8", - "bathtub": "unicode/1f6c1.png?v8", - "battery": "unicode/1f50b.png?v8", - "beach_umbrella": "unicode/1f3d6.png?v8", - "bear": "unicode/1f43b.png?v8", - "bearded_person": "unicode/1f9d4.png?v8", - "beaver": "unicode/1f9ab.png?v8", - "bed": "unicode/1f6cf.png?v8", - "bee": "unicode/1f41d.png?v8", - "beer": "unicode/1f37a.png?v8", - "beers": "unicode/1f37b.png?v8", - "beetle": "unicode/1fab2.png?v8", - "beginner": "unicode/1f530.png?v8", - "belarus": "unicode/1f1e7-1f1fe.png?v8", - "belgium": "unicode/1f1e7-1f1ea.png?v8", - "belize": "unicode/1f1e7-1f1ff.png?v8", - "bell": "unicode/1f514.png?v8", - "bell_pepper": "unicode/1fad1.png?v8", - "bellhop_bell": "unicode/1f6ce.png?v8", - "benin": "unicode/1f1e7-1f1ef.png?v8", - "bento": "unicode/1f371.png?v8", - "bermuda": "unicode/1f1e7-1f1f2.png?v8", - "beverage_box": "unicode/1f9c3.png?v8", - "bhutan": "unicode/1f1e7-1f1f9.png?v8", - "bicyclist": "unicode/1f6b4.png?v8", - "bike": "unicode/1f6b2.png?v8", - "biking_man": "unicode/1f6b4-2642.png?v8", - "biking_woman": "unicode/1f6b4-2640.png?v8", - "bikini": "unicode/1f459.png?v8", - "billed_cap": "unicode/1f9e2.png?v8", - "biohazard": "unicode/2623.png?v8", - "bird": "unicode/1f426.png?v8", - "birthday": "unicode/1f382.png?v8", - "bison": "unicode/1f9ac.png?v8", - "black_cat": "unicode/1f408-2b1b.png?v8", - "black_circle": "unicode/26ab.png?v8", - "black_flag": "unicode/1f3f4.png?v8", - "black_heart": "unicode/1f5a4.png?v8", - "black_joker": "unicode/1f0cf.png?v8", - "black_large_square": "unicode/2b1b.png?v8", - "black_medium_small_square": "unicode/25fe.png?v8", - "black_medium_square": "unicode/25fc.png?v8", - "black_nib": "unicode/2712.png?v8", - "black_small_square": "unicode/25aa.png?v8", - "black_square_button": "unicode/1f532.png?v8", - "blond_haired_man": "unicode/1f471-2642.png?v8", - "blond_haired_person": "unicode/1f471.png?v8", - "blond_haired_woman": "unicode/1f471-2640.png?v8", - "blonde_woman": "unicode/1f471-2640.png?v8", - "blossom": "unicode/1f33c.png?v8", - "blowfish": "unicode/1f421.png?v8", - "blue_book": "unicode/1f4d8.png?v8", - "blue_car": "unicode/1f699.png?v8", - "blue_heart": "unicode/1f499.png?v8", - "blue_square": "unicode/1f7e6.png?v8", - "blueberries": "unicode/1fad0.png?v8", - "blush": "unicode/1f60a.png?v8", - "boar": "unicode/1f417.png?v8", - "boat": "unicode/26f5.png?v8", - "bolivia": "unicode/1f1e7-1f1f4.png?v8", - "bomb": "unicode/1f4a3.png?v8", - "bone": "unicode/1f9b4.png?v8", - "book": "unicode/1f4d6.png?v8", - "bookmark": "unicode/1f516.png?v8", - "bookmark_tabs": "unicode/1f4d1.png?v8", - "books": "unicode/1f4da.png?v8", - "boom": "unicode/1f4a5.png?v8", - "boomerang": "unicode/1fa83.png?v8", - "boot": "unicode/1f462.png?v8", - "bosnia_herzegovina": "unicode/1f1e7-1f1e6.png?v8", - "botswana": "unicode/1f1e7-1f1fc.png?v8", - "bouncing_ball_man": "unicode/26f9-2642.png?v8", - "bouncing_ball_person": "unicode/26f9.png?v8", - "bouncing_ball_woman": "unicode/26f9-2640.png?v8", - "bouquet": "unicode/1f490.png?v8", - "bouvet_island": "unicode/1f1e7-1f1fb.png?v8", - "bow": "unicode/1f647.png?v8", - "bow_and_arrow": "unicode/1f3f9.png?v8", - "bowing_man": "unicode/1f647-2642.png?v8", - "bowing_woman": "unicode/1f647-2640.png?v8", - "bowl_with_spoon": "unicode/1f963.png?v8", - "bowling": "unicode/1f3b3.png?v8", - "bowtie": "bowtie.png?v8", - "boxing_glove": "unicode/1f94a.png?v8", - "boy": "unicode/1f466.png?v8", - "brain": "unicode/1f9e0.png?v8", - "brazil": "unicode/1f1e7-1f1f7.png?v8", - "bread": "unicode/1f35e.png?v8", - "breast_feeding": "unicode/1f931.png?v8", - "bricks": "unicode/1f9f1.png?v8", - "bride_with_veil": "unicode/1f470-2640.png?v8", - "bridge_at_night": "unicode/1f309.png?v8", - "briefcase": "unicode/1f4bc.png?v8", - "british_indian_ocean_territory": "unicode/1f1ee-1f1f4.png?v8", - "british_virgin_islands": "unicode/1f1fb-1f1ec.png?v8", - "broccoli": "unicode/1f966.png?v8", - "broken_heart": "unicode/1f494.png?v8", - "broom": "unicode/1f9f9.png?v8", - "brown_circle": "unicode/1f7e4.png?v8", - "brown_heart": "unicode/1f90e.png?v8", - "brown_square": "unicode/1f7eb.png?v8", - "brunei": "unicode/1f1e7-1f1f3.png?v8", - "bubble_tea": "unicode/1f9cb.png?v8", - "bucket": "unicode/1faa3.png?v8", - "bug": "unicode/1f41b.png?v8", - "building_construction": "unicode/1f3d7.png?v8", - "bulb": "unicode/1f4a1.png?v8", - "bulgaria": "unicode/1f1e7-1f1ec.png?v8", - "bullettrain_front": "unicode/1f685.png?v8", - "bullettrain_side": "unicode/1f684.png?v8", - "burkina_faso": "unicode/1f1e7-1f1eb.png?v8", - "burrito": "unicode/1f32f.png?v8", - "burundi": "unicode/1f1e7-1f1ee.png?v8", - "bus": "unicode/1f68c.png?v8", - "business_suit_levitating": "unicode/1f574.png?v8", - "busstop": "unicode/1f68f.png?v8", - "bust_in_silhouette": "unicode/1f464.png?v8", - "busts_in_silhouette": "unicode/1f465.png?v8", - "butter": "unicode/1f9c8.png?v8", - "butterfly": "unicode/1f98b.png?v8", - "cactus": "unicode/1f335.png?v8", - "cake": "unicode/1f370.png?v8", - "calendar": "unicode/1f4c6.png?v8", - "call_me_hand": "unicode/1f919.png?v8", - "calling": "unicode/1f4f2.png?v8", - "cambodia": "unicode/1f1f0-1f1ed.png?v8", - "camel": "unicode/1f42b.png?v8", - "camera": "unicode/1f4f7.png?v8", - "camera_flash": "unicode/1f4f8.png?v8", - "cameroon": "unicode/1f1e8-1f1f2.png?v8", - "camping": "unicode/1f3d5.png?v8", - "canada": "unicode/1f1e8-1f1e6.png?v8", - "canary_islands": "unicode/1f1ee-1f1e8.png?v8", - "cancer": "unicode/264b.png?v8", - "candle": "unicode/1f56f.png?v8", - "candy": "unicode/1f36c.png?v8", - "canned_food": "unicode/1f96b.png?v8", - "canoe": "unicode/1f6f6.png?v8", - "cape_verde": "unicode/1f1e8-1f1fb.png?v8", - "capital_abcd": "unicode/1f520.png?v8", - "capricorn": "unicode/2651.png?v8", - "car": "unicode/1f697.png?v8", - "card_file_box": "unicode/1f5c3.png?v8", - "card_index": "unicode/1f4c7.png?v8", - "card_index_dividers": "unicode/1f5c2.png?v8", - "caribbean_netherlands": "unicode/1f1e7-1f1f6.png?v8", - "carousel_horse": "unicode/1f3a0.png?v8", - "carpentry_saw": "unicode/1fa9a.png?v8", - "carrot": "unicode/1f955.png?v8", - "cartwheeling": "unicode/1f938.png?v8", - "cat": "unicode/1f431.png?v8", - "cat2": "unicode/1f408.png?v8", - "cayman_islands": "unicode/1f1f0-1f1fe.png?v8", - "cd": "unicode/1f4bf.png?v8", - "central_african_republic": "unicode/1f1e8-1f1eb.png?v8", - "ceuta_melilla": "unicode/1f1ea-1f1e6.png?v8", - "chad": "unicode/1f1f9-1f1e9.png?v8", - "chains": "unicode/26d3.png?v8", - "chair": "unicode/1fa91.png?v8", - "champagne": "unicode/1f37e.png?v8", - "chart": "unicode/1f4b9.png?v8", - "chart_with_downwards_trend": "unicode/1f4c9.png?v8", - "chart_with_upwards_trend": "unicode/1f4c8.png?v8", - "checkered_flag": "unicode/1f3c1.png?v8", - "cheese": "unicode/1f9c0.png?v8", - "cherries": "unicode/1f352.png?v8", - "cherry_blossom": "unicode/1f338.png?v8", - "chess_pawn": "unicode/265f.png?v8", - "chestnut": "unicode/1f330.png?v8", - "chicken": "unicode/1f414.png?v8", - "child": "unicode/1f9d2.png?v8", - "children_crossing": "unicode/1f6b8.png?v8", - "chile": "unicode/1f1e8-1f1f1.png?v8", - "chipmunk": "unicode/1f43f.png?v8", - "chocolate_bar": "unicode/1f36b.png?v8", - "chopsticks": "unicode/1f962.png?v8", - "christmas_island": "unicode/1f1e8-1f1fd.png?v8", - "christmas_tree": "unicode/1f384.png?v8", - "church": "unicode/26ea.png?v8", - "cinema": "unicode/1f3a6.png?v8", - "circus_tent": "unicode/1f3aa.png?v8", - "city_sunrise": "unicode/1f307.png?v8", - "city_sunset": "unicode/1f306.png?v8", - "cityscape": "unicode/1f3d9.png?v8", - "cl": "unicode/1f191.png?v8", - "clamp": "unicode/1f5dc.png?v8", - "clap": "unicode/1f44f.png?v8", - "clapper": "unicode/1f3ac.png?v8", - "classical_building": "unicode/1f3db.png?v8", - "climbing": "unicode/1f9d7.png?v8", - "climbing_man": "unicode/1f9d7-2642.png?v8", - "climbing_woman": "unicode/1f9d7-2640.png?v8", - "clinking_glasses": "unicode/1f942.png?v8", - "clipboard": "unicode/1f4cb.png?v8", - "clipperton_island": "unicode/1f1e8-1f1f5.png?v8", - "clock1": "unicode/1f550.png?v8", - "clock10": "unicode/1f559.png?v8", - "clock1030": "unicode/1f565.png?v8", - "clock11": "unicode/1f55a.png?v8", - "clock1130": "unicode/1f566.png?v8", - "clock12": "unicode/1f55b.png?v8", - "clock1230": "unicode/1f567.png?v8", - "clock130": "unicode/1f55c.png?v8", - "clock2": "unicode/1f551.png?v8", - "clock230": "unicode/1f55d.png?v8", - "clock3": "unicode/1f552.png?v8", - "clock330": "unicode/1f55e.png?v8", - "clock4": "unicode/1f553.png?v8", - "clock430": "unicode/1f55f.png?v8", - "clock5": "unicode/1f554.png?v8", - "clock530": "unicode/1f560.png?v8", - "clock6": "unicode/1f555.png?v8", - "clock630": "unicode/1f561.png?v8", - "clock7": "unicode/1f556.png?v8", - "clock730": "unicode/1f562.png?v8", - "clock8": "unicode/1f557.png?v8", - "clock830": "unicode/1f563.png?v8", - "clock9": "unicode/1f558.png?v8", - "clock930": "unicode/1f564.png?v8", - "closed_book": "unicode/1f4d5.png?v8", - "closed_lock_with_key": "unicode/1f510.png?v8", - "closed_umbrella": "unicode/1f302.png?v8", - "cloud": "unicode/2601.png?v8", - "cloud_with_lightning": "unicode/1f329.png?v8", - "cloud_with_lightning_and_rain": "unicode/26c8.png?v8", - "cloud_with_rain": "unicode/1f327.png?v8", - "cloud_with_snow": "unicode/1f328.png?v8", - "clown_face": "unicode/1f921.png?v8", - "clubs": "unicode/2663.png?v8", - "cn": "unicode/1f1e8-1f1f3.png?v8", - "coat": "unicode/1f9e5.png?v8", - "cockroach": "unicode/1fab3.png?v8", - "cocktail": "unicode/1f378.png?v8", - "coconut": "unicode/1f965.png?v8", - "cocos_islands": "unicode/1f1e8-1f1e8.png?v8", - "coffee": "unicode/2615.png?v8", - "coffin": "unicode/26b0.png?v8", - "coin": "unicode/1fa99.png?v8", - "cold_face": "unicode/1f976.png?v8", - "cold_sweat": "unicode/1f630.png?v8", - "collision": "unicode/1f4a5.png?v8", - "colombia": "unicode/1f1e8-1f1f4.png?v8", - "comet": "unicode/2604.png?v8", - "comoros": "unicode/1f1f0-1f1f2.png?v8", - "compass": "unicode/1f9ed.png?v8", - "computer": "unicode/1f4bb.png?v8", - "computer_mouse": "unicode/1f5b1.png?v8", - "confetti_ball": "unicode/1f38a.png?v8", - "confounded": "unicode/1f616.png?v8", - "confused": "unicode/1f615.png?v8", - "congo_brazzaville": "unicode/1f1e8-1f1ec.png?v8", - "congo_kinshasa": "unicode/1f1e8-1f1e9.png?v8", - "congratulations": "unicode/3297.png?v8", - "construction": "unicode/1f6a7.png?v8", - "construction_worker": "unicode/1f477.png?v8", - "construction_worker_man": "unicode/1f477-2642.png?v8", - "construction_worker_woman": "unicode/1f477-2640.png?v8", - "control_knobs": "unicode/1f39b.png?v8", - "convenience_store": "unicode/1f3ea.png?v8", - "cook": "unicode/1f9d1-1f373.png?v8", - "cook_islands": "unicode/1f1e8-1f1f0.png?v8", - "cookie": "unicode/1f36a.png?v8", - "cool": "unicode/1f192.png?v8", - "cop": "unicode/1f46e.png?v8", - "copyright": "unicode/00a9.png?v8", - "corn": "unicode/1f33d.png?v8", - "costa_rica": "unicode/1f1e8-1f1f7.png?v8", - "cote_divoire": "unicode/1f1e8-1f1ee.png?v8", - "couch_and_lamp": "unicode/1f6cb.png?v8", - "couple": "unicode/1f46b.png?v8", - "couple_with_heart": "unicode/1f491.png?v8", - "couple_with_heart_man_man": "unicode/1f468-2764-1f468.png?v8", - "couple_with_heart_woman_man": "unicode/1f469-2764-1f468.png?v8", - "couple_with_heart_woman_woman": "unicode/1f469-2764-1f469.png?v8", - "couplekiss": "unicode/1f48f.png?v8", - "couplekiss_man_man": "unicode/1f468-2764-1f48b-1f468.png?v8", - "couplekiss_man_woman": "unicode/1f469-2764-1f48b-1f468.png?v8", - "couplekiss_woman_woman": "unicode/1f469-2764-1f48b-1f469.png?v8", - "cow": "unicode/1f42e.png?v8", - "cow2": "unicode/1f404.png?v8", - "cowboy_hat_face": "unicode/1f920.png?v8", - "crab": "unicode/1f980.png?v8", - "crayon": "unicode/1f58d.png?v8", - "credit_card": "unicode/1f4b3.png?v8", - "crescent_moon": "unicode/1f319.png?v8", - "cricket": "unicode/1f997.png?v8", - "cricket_game": "unicode/1f3cf.png?v8", - "croatia": "unicode/1f1ed-1f1f7.png?v8", - "crocodile": "unicode/1f40a.png?v8", - "croissant": "unicode/1f950.png?v8", - "crossed_fingers": "unicode/1f91e.png?v8", - "crossed_flags": "unicode/1f38c.png?v8", - "crossed_swords": "unicode/2694.png?v8", - "crown": "unicode/1f451.png?v8", - "cry": "unicode/1f622.png?v8", - "crying_cat_face": "unicode/1f63f.png?v8", - "crystal_ball": "unicode/1f52e.png?v8", - "cuba": "unicode/1f1e8-1f1fa.png?v8", - "cucumber": "unicode/1f952.png?v8", - "cup_with_straw": "unicode/1f964.png?v8", - "cupcake": "unicode/1f9c1.png?v8", - "cupid": "unicode/1f498.png?v8", - "curacao": "unicode/1f1e8-1f1fc.png?v8", - "curling_stone": "unicode/1f94c.png?v8", - "curly_haired_man": "unicode/1f468-1f9b1.png?v8", - "curly_haired_woman": "unicode/1f469-1f9b1.png?v8", - "curly_loop": "unicode/27b0.png?v8", - "currency_exchange": "unicode/1f4b1.png?v8", - "curry": "unicode/1f35b.png?v8", - "cursing_face": "unicode/1f92c.png?v8", - "custard": "unicode/1f36e.png?v8", - "customs": "unicode/1f6c3.png?v8", - "cut_of_meat": "unicode/1f969.png?v8", - "cyclone": "unicode/1f300.png?v8", - "cyprus": "unicode/1f1e8-1f1fe.png?v8", - "czech_republic": "unicode/1f1e8-1f1ff.png?v8", - "dagger": "unicode/1f5e1.png?v8", - "dancer": "unicode/1f483.png?v8", - "dancers": "unicode/1f46f.png?v8", - "dancing_men": "unicode/1f46f-2642.png?v8", - "dancing_women": "unicode/1f46f-2640.png?v8", - "dango": "unicode/1f361.png?v8", - "dark_sunglasses": "unicode/1f576.png?v8", - "dart": "unicode/1f3af.png?v8", - "dash": "unicode/1f4a8.png?v8", - "date": "unicode/1f4c5.png?v8", - "de": "unicode/1f1e9-1f1ea.png?v8", - "deaf_man": "unicode/1f9cf-2642.png?v8", - "deaf_person": "unicode/1f9cf.png?v8", - "deaf_woman": "unicode/1f9cf-2640.png?v8", - "deciduous_tree": "unicode/1f333.png?v8", - "deer": "unicode/1f98c.png?v8", - "denmark": "unicode/1f1e9-1f1f0.png?v8", - "department_store": "unicode/1f3ec.png?v8", - "derelict_house": "unicode/1f3da.png?v8", - "desert": "unicode/1f3dc.png?v8", - "desert_island": "unicode/1f3dd.png?v8", - "desktop_computer": "unicode/1f5a5.png?v8", - "detective": "unicode/1f575.png?v8", - "diamond_shape_with_a_dot_inside": "unicode/1f4a0.png?v8", - "diamonds": "unicode/2666.png?v8", - "diego_garcia": "unicode/1f1e9-1f1ec.png?v8", - "disappointed": "unicode/1f61e.png?v8", - "disappointed_relieved": "unicode/1f625.png?v8", - "disguised_face": "unicode/1f978.png?v8", - "diving_mask": "unicode/1f93f.png?v8", - "diya_lamp": "unicode/1fa94.png?v8", - "dizzy": "unicode/1f4ab.png?v8", - "dizzy_face": "unicode/1f635.png?v8", - "djibouti": "unicode/1f1e9-1f1ef.png?v8", - "dna": "unicode/1f9ec.png?v8", - "do_not_litter": "unicode/1f6af.png?v8", - "dodo": "unicode/1f9a4.png?v8", - "dog": "unicode/1f436.png?v8", - "dog2": "unicode/1f415.png?v8", - "dollar": "unicode/1f4b5.png?v8", - "dolls": "unicode/1f38e.png?v8", - "dolphin": "unicode/1f42c.png?v8", - "dominica": "unicode/1f1e9-1f1f2.png?v8", - "dominican_republic": "unicode/1f1e9-1f1f4.png?v8", - "door": "unicode/1f6aa.png?v8", - "doughnut": "unicode/1f369.png?v8", - "dove": "unicode/1f54a.png?v8", - "dragon": "unicode/1f409.png?v8", - "dragon_face": "unicode/1f432.png?v8", - "dress": "unicode/1f457.png?v8", - "dromedary_camel": "unicode/1f42a.png?v8", - "drooling_face": "unicode/1f924.png?v8", - "drop_of_blood": "unicode/1fa78.png?v8", - "droplet": "unicode/1f4a7.png?v8", - "drum": "unicode/1f941.png?v8", - "duck": "unicode/1f986.png?v8", - "dumpling": "unicode/1f95f.png?v8", - "dvd": "unicode/1f4c0.png?v8", - "e-mail": "unicode/1f4e7.png?v8", - "eagle": "unicode/1f985.png?v8", - "ear": "unicode/1f442.png?v8", - "ear_of_rice": "unicode/1f33e.png?v8", - "ear_with_hearing_aid": "unicode/1f9bb.png?v8", - "earth_africa": "unicode/1f30d.png?v8", - "earth_americas": "unicode/1f30e.png?v8", - "earth_asia": "unicode/1f30f.png?v8", - "ecuador": "unicode/1f1ea-1f1e8.png?v8", - "egg": "unicode/1f95a.png?v8", - "eggplant": "unicode/1f346.png?v8", - "egypt": "unicode/1f1ea-1f1ec.png?v8", - "eight": "unicode/0038-20e3.png?v8", - "eight_pointed_black_star": "unicode/2734.png?v8", - "eight_spoked_asterisk": "unicode/2733.png?v8", - "eject_button": "unicode/23cf.png?v8", - "el_salvador": "unicode/1f1f8-1f1fb.png?v8", - "electric_plug": "unicode/1f50c.png?v8", - "electron": "electron.png?v8", - "elephant": "unicode/1f418.png?v8", - "elevator": "unicode/1f6d7.png?v8", - "elf": "unicode/1f9dd.png?v8", - "elf_man": "unicode/1f9dd-2642.png?v8", - "elf_woman": "unicode/1f9dd-2640.png?v8", - "email": "unicode/1f4e7.png?v8", - "end": "unicode/1f51a.png?v8", - "england": "unicode/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f.png?v8", - "envelope": "unicode/2709.png?v8", - "envelope_with_arrow": "unicode/1f4e9.png?v8", - "equatorial_guinea": "unicode/1f1ec-1f1f6.png?v8", - "eritrea": "unicode/1f1ea-1f1f7.png?v8", - "es": "unicode/1f1ea-1f1f8.png?v8", - "estonia": "unicode/1f1ea-1f1ea.png?v8", - "ethiopia": "unicode/1f1ea-1f1f9.png?v8", - "eu": "unicode/1f1ea-1f1fa.png?v8", - "euro": "unicode/1f4b6.png?v8", - "european_castle": "unicode/1f3f0.png?v8", - "european_post_office": "unicode/1f3e4.png?v8", - "european_union": "unicode/1f1ea-1f1fa.png?v8", - "evergreen_tree": "unicode/1f332.png?v8", - "exclamation": "unicode/2757.png?v8", - "exploding_head": "unicode/1f92f.png?v8", - "expressionless": "unicode/1f611.png?v8", - "eye": "unicode/1f441.png?v8", - "eye_speech_bubble": "unicode/1f441-1f5e8.png?v8", - "eyeglasses": "unicode/1f453.png?v8", - "eyes": "unicode/1f440.png?v8", - "face_exhaling": "unicode/1f62e-1f4a8.png?v8", - "face_in_clouds": "unicode/1f636-1f32b.png?v8", - "face_with_head_bandage": "unicode/1f915.png?v8", - "face_with_spiral_eyes": "unicode/1f635-1f4ab.png?v8", - "face_with_thermometer": "unicode/1f912.png?v8", - "facepalm": "unicode/1f926.png?v8", - "facepunch": "unicode/1f44a.png?v8", - "factory": "unicode/1f3ed.png?v8", - "factory_worker": "unicode/1f9d1-1f3ed.png?v8", - "fairy": "unicode/1f9da.png?v8", - "fairy_man": "unicode/1f9da-2642.png?v8", - "fairy_woman": "unicode/1f9da-2640.png?v8", - "falafel": "unicode/1f9c6.png?v8", - "falkland_islands": "unicode/1f1eb-1f1f0.png?v8", - "fallen_leaf": "unicode/1f342.png?v8", - "family": "unicode/1f46a.png?v8", - "family_man_boy": "unicode/1f468-1f466.png?v8", - "family_man_boy_boy": "unicode/1f468-1f466-1f466.png?v8", - "family_man_girl": "unicode/1f468-1f467.png?v8", - "family_man_girl_boy": "unicode/1f468-1f467-1f466.png?v8", - "family_man_girl_girl": "unicode/1f468-1f467-1f467.png?v8", - "family_man_man_boy": "unicode/1f468-1f468-1f466.png?v8", - "family_man_man_boy_boy": "unicode/1f468-1f468-1f466-1f466.png?v8", - "family_man_man_girl": "unicode/1f468-1f468-1f467.png?v8", - "family_man_man_girl_boy": "unicode/1f468-1f468-1f467-1f466.png?v8", - "family_man_man_girl_girl": "unicode/1f468-1f468-1f467-1f467.png?v8", - "family_man_woman_boy": "unicode/1f468-1f469-1f466.png?v8", - "family_man_woman_boy_boy": "unicode/1f468-1f469-1f466-1f466.png?v8", - "family_man_woman_girl": "unicode/1f468-1f469-1f467.png?v8", - "family_man_woman_girl_boy": "unicode/1f468-1f469-1f467-1f466.png?v8", - "family_man_woman_girl_girl": "unicode/1f468-1f469-1f467-1f467.png?v8", - "family_woman_boy": "unicode/1f469-1f466.png?v8", - "family_woman_boy_boy": "unicode/1f469-1f466-1f466.png?v8", - "family_woman_girl": "unicode/1f469-1f467.png?v8", - "family_woman_girl_boy": "unicode/1f469-1f467-1f466.png?v8", - "family_woman_girl_girl": "unicode/1f469-1f467-1f467.png?v8", - "family_woman_woman_boy": "unicode/1f469-1f469-1f466.png?v8", - "family_woman_woman_boy_boy": "unicode/1f469-1f469-1f466-1f466.png?v8", - "family_woman_woman_girl": "unicode/1f469-1f469-1f467.png?v8", - "family_woman_woman_girl_boy": "unicode/1f469-1f469-1f467-1f466.png?v8", - "family_woman_woman_girl_girl": "unicode/1f469-1f469-1f467-1f467.png?v8", - "farmer": "unicode/1f9d1-1f33e.png?v8", - "faroe_islands": "unicode/1f1eb-1f1f4.png?v8", - "fast_forward": "unicode/23e9.png?v8", - "fax": "unicode/1f4e0.png?v8", - "fearful": "unicode/1f628.png?v8", - "feather": "unicode/1fab6.png?v8", - "feelsgood": "feelsgood.png?v8", - "feet": "unicode/1f43e.png?v8", - "female_detective": "unicode/1f575-2640.png?v8", - "female_sign": "unicode/2640.png?v8", - "ferris_wheel": "unicode/1f3a1.png?v8", - "ferry": "unicode/26f4.png?v8", - "field_hockey": "unicode/1f3d1.png?v8", - "fiji": "unicode/1f1eb-1f1ef.png?v8", - "file_cabinet": "unicode/1f5c4.png?v8", - "file_folder": "unicode/1f4c1.png?v8", - "film_projector": "unicode/1f4fd.png?v8", - "film_strip": "unicode/1f39e.png?v8", - "finland": "unicode/1f1eb-1f1ee.png?v8", - "finnadie": "finnadie.png?v8", - "fire": "unicode/1f525.png?v8", - "fire_engine": "unicode/1f692.png?v8", - "fire_extinguisher": "unicode/1f9ef.png?v8", - "firecracker": "unicode/1f9e8.png?v8", - "firefighter": "unicode/1f9d1-1f692.png?v8", - "fireworks": "unicode/1f386.png?v8", - "first_quarter_moon": "unicode/1f313.png?v8", - "first_quarter_moon_with_face": "unicode/1f31b.png?v8", - "fish": "unicode/1f41f.png?v8", - "fish_cake": "unicode/1f365.png?v8", - "fishing_pole_and_fish": "unicode/1f3a3.png?v8", - "fist": "unicode/270a.png?v8", - "fist_left": "unicode/1f91b.png?v8", - "fist_oncoming": "unicode/1f44a.png?v8", - "fist_raised": "unicode/270a.png?v8", - "fist_right": "unicode/1f91c.png?v8", - "five": "unicode/0035-20e3.png?v8", - "flags": "unicode/1f38f.png?v8", - "flamingo": "unicode/1f9a9.png?v8", - "flashlight": "unicode/1f526.png?v8", - "flat_shoe": "unicode/1f97f.png?v8", - "flatbread": "unicode/1fad3.png?v8", - "fleur_de_lis": "unicode/269c.png?v8", - "flight_arrival": "unicode/1f6ec.png?v8", - "flight_departure": "unicode/1f6eb.png?v8", - "flipper": "unicode/1f42c.png?v8", - "floppy_disk": "unicode/1f4be.png?v8", - "flower_playing_cards": "unicode/1f3b4.png?v8", - "flushed": "unicode/1f633.png?v8", - "fly": "unicode/1fab0.png?v8", - "flying_disc": "unicode/1f94f.png?v8", - "flying_saucer": "unicode/1f6f8.png?v8", - "fog": "unicode/1f32b.png?v8", - "foggy": "unicode/1f301.png?v8", - "fondue": "unicode/1fad5.png?v8", - "foot": "unicode/1f9b6.png?v8", - "football": "unicode/1f3c8.png?v8", - "footprints": "unicode/1f463.png?v8", - "fork_and_knife": "unicode/1f374.png?v8", - "fortune_cookie": "unicode/1f960.png?v8", - "fountain": "unicode/26f2.png?v8", - "fountain_pen": "unicode/1f58b.png?v8", - "four": "unicode/0034-20e3.png?v8", - "four_leaf_clover": "unicode/1f340.png?v8", - "fox_face": "unicode/1f98a.png?v8", - "fr": "unicode/1f1eb-1f1f7.png?v8", - "framed_picture": "unicode/1f5bc.png?v8", - "free": "unicode/1f193.png?v8", - "french_guiana": "unicode/1f1ec-1f1eb.png?v8", - "french_polynesia": "unicode/1f1f5-1f1eb.png?v8", - "french_southern_territories": "unicode/1f1f9-1f1eb.png?v8", - "fried_egg": "unicode/1f373.png?v8", - "fried_shrimp": "unicode/1f364.png?v8", - "fries": "unicode/1f35f.png?v8", - "frog": "unicode/1f438.png?v8", - "frowning": "unicode/1f626.png?v8", - "frowning_face": "unicode/2639.png?v8", - "frowning_man": "unicode/1f64d-2642.png?v8", - "frowning_person": "unicode/1f64d.png?v8", - "frowning_woman": "unicode/1f64d-2640.png?v8", - "fu": "unicode/1f595.png?v8", - "fuelpump": "unicode/26fd.png?v8", - "full_moon": "unicode/1f315.png?v8", - "full_moon_with_face": "unicode/1f31d.png?v8", - "funeral_urn": "unicode/26b1.png?v8", - "gabon": "unicode/1f1ec-1f1e6.png?v8", - "gambia": "unicode/1f1ec-1f1f2.png?v8", - "game_die": "unicode/1f3b2.png?v8", - "garlic": "unicode/1f9c4.png?v8", - "gb": "unicode/1f1ec-1f1e7.png?v8", - "gear": "unicode/2699.png?v8", - "gem": "unicode/1f48e.png?v8", - "gemini": "unicode/264a.png?v8", - "genie": "unicode/1f9de.png?v8", - "genie_man": "unicode/1f9de-2642.png?v8", - "genie_woman": "unicode/1f9de-2640.png?v8", - "georgia": "unicode/1f1ec-1f1ea.png?v8", - "ghana": "unicode/1f1ec-1f1ed.png?v8", - "ghost": "unicode/1f47b.png?v8", - "gibraltar": "unicode/1f1ec-1f1ee.png?v8", - "gift": "unicode/1f381.png?v8", - "gift_heart": "unicode/1f49d.png?v8", - "giraffe": "unicode/1f992.png?v8", - "girl": "unicode/1f467.png?v8", - "globe_with_meridians": "unicode/1f310.png?v8", - "gloves": "unicode/1f9e4.png?v8", - "goal_net": "unicode/1f945.png?v8", - "goat": "unicode/1f410.png?v8", - "goberserk": "goberserk.png?v8", - "godmode": "godmode.png?v8", - "goggles": "unicode/1f97d.png?v8", - "golf": "unicode/26f3.png?v8", - "golfing": "unicode/1f3cc.png?v8", - "golfing_man": "unicode/1f3cc-2642.png?v8", - "golfing_woman": "unicode/1f3cc-2640.png?v8", - "gorilla": "unicode/1f98d.png?v8", - "grapes": "unicode/1f347.png?v8", - "greece": "unicode/1f1ec-1f1f7.png?v8", - "green_apple": "unicode/1f34f.png?v8", - "green_book": "unicode/1f4d7.png?v8", - "green_circle": "unicode/1f7e2.png?v8", - "green_heart": "unicode/1f49a.png?v8", - "green_salad": "unicode/1f957.png?v8", - "green_square": "unicode/1f7e9.png?v8", - "greenland": "unicode/1f1ec-1f1f1.png?v8", - "grenada": "unicode/1f1ec-1f1e9.png?v8", - "grey_exclamation": "unicode/2755.png?v8", - "grey_question": "unicode/2754.png?v8", - "grimacing": "unicode/1f62c.png?v8", - "grin": "unicode/1f601.png?v8", - "grinning": "unicode/1f600.png?v8", - "guadeloupe": "unicode/1f1ec-1f1f5.png?v8", - "guam": "unicode/1f1ec-1f1fa.png?v8", - "guard": "unicode/1f482.png?v8", - "guardsman": "unicode/1f482-2642.png?v8", - "guardswoman": "unicode/1f482-2640.png?v8", - "guatemala": "unicode/1f1ec-1f1f9.png?v8", - "guernsey": "unicode/1f1ec-1f1ec.png?v8", - "guide_dog": "unicode/1f9ae.png?v8", - "guinea": "unicode/1f1ec-1f1f3.png?v8", - "guinea_bissau": "unicode/1f1ec-1f1fc.png?v8", - "guitar": "unicode/1f3b8.png?v8", - "gun": "unicode/1f52b.png?v8", - "guyana": "unicode/1f1ec-1f1fe.png?v8", - "haircut": "unicode/1f487.png?v8", - "haircut_man": "unicode/1f487-2642.png?v8", - "haircut_woman": "unicode/1f487-2640.png?v8", - "haiti": "unicode/1f1ed-1f1f9.png?v8", - "hamburger": "unicode/1f354.png?v8", - "hammer": "unicode/1f528.png?v8", - "hammer_and_pick": "unicode/2692.png?v8", - "hammer_and_wrench": "unicode/1f6e0.png?v8", - "hamster": "unicode/1f439.png?v8", - "hand": "unicode/270b.png?v8", - "hand_over_mouth": "unicode/1f92d.png?v8", - "handbag": "unicode/1f45c.png?v8", - "handball_person": "unicode/1f93e.png?v8", - "handshake": "unicode/1f91d.png?v8", - "hankey": "unicode/1f4a9.png?v8", - "hash": "unicode/0023-20e3.png?v8", - "hatched_chick": "unicode/1f425.png?v8", - "hatching_chick": "unicode/1f423.png?v8", - "headphones": "unicode/1f3a7.png?v8", - "headstone": "unicode/1faa6.png?v8", - "health_worker": "unicode/1f9d1-2695.png?v8", - "hear_no_evil": "unicode/1f649.png?v8", - "heard_mcdonald_islands": "unicode/1f1ed-1f1f2.png?v8", - "heart": "unicode/2764.png?v8", - "heart_decoration": "unicode/1f49f.png?v8", - "heart_eyes": "unicode/1f60d.png?v8", - "heart_eyes_cat": "unicode/1f63b.png?v8", - "heart_on_fire": "unicode/2764-1f525.png?v8", - "heartbeat": "unicode/1f493.png?v8", - "heartpulse": "unicode/1f497.png?v8", - "hearts": "unicode/2665.png?v8", - "heavy_check_mark": "unicode/2714.png?v8", - "heavy_division_sign": "unicode/2797.png?v8", - "heavy_dollar_sign": "unicode/1f4b2.png?v8", - "heavy_exclamation_mark": "unicode/2757.png?v8", - "heavy_heart_exclamation": "unicode/2763.png?v8", - "heavy_minus_sign": "unicode/2796.png?v8", - "heavy_multiplication_x": "unicode/2716.png?v8", - "heavy_plus_sign": "unicode/2795.png?v8", - "hedgehog": "unicode/1f994.png?v8", - "helicopter": "unicode/1f681.png?v8", - "herb": "unicode/1f33f.png?v8", - "hibiscus": "unicode/1f33a.png?v8", - "high_brightness": "unicode/1f506.png?v8", - "high_heel": "unicode/1f460.png?v8", - "hiking_boot": "unicode/1f97e.png?v8", - "hindu_temple": "unicode/1f6d5.png?v8", - "hippopotamus": "unicode/1f99b.png?v8", - "hocho": "unicode/1f52a.png?v8", - "hole": "unicode/1f573.png?v8", - "honduras": "unicode/1f1ed-1f1f3.png?v8", - "honey_pot": "unicode/1f36f.png?v8", - "honeybee": "unicode/1f41d.png?v8", - "hong_kong": "unicode/1f1ed-1f1f0.png?v8", - "hook": "unicode/1fa9d.png?v8", - "horse": "unicode/1f434.png?v8", - "horse_racing": "unicode/1f3c7.png?v8", - "hospital": "unicode/1f3e5.png?v8", - "hot_face": "unicode/1f975.png?v8", - "hot_pepper": "unicode/1f336.png?v8", - "hotdog": "unicode/1f32d.png?v8", - "hotel": "unicode/1f3e8.png?v8", - "hotsprings": "unicode/2668.png?v8", - "hourglass": "unicode/231b.png?v8", - "hourglass_flowing_sand": "unicode/23f3.png?v8", - "house": "unicode/1f3e0.png?v8", - "house_with_garden": "unicode/1f3e1.png?v8", - "houses": "unicode/1f3d8.png?v8", - "hugs": "unicode/1f917.png?v8", - "hungary": "unicode/1f1ed-1f1fa.png?v8", - "hurtrealbad": "hurtrealbad.png?v8", - "hushed": "unicode/1f62f.png?v8", - "hut": "unicode/1f6d6.png?v8", - "ice_cream": "unicode/1f368.png?v8", - "ice_cube": "unicode/1f9ca.png?v8", - "ice_hockey": "unicode/1f3d2.png?v8", - "ice_skate": "unicode/26f8.png?v8", - "icecream": "unicode/1f366.png?v8", - "iceland": "unicode/1f1ee-1f1f8.png?v8", - "id": "unicode/1f194.png?v8", - "ideograph_advantage": "unicode/1f250.png?v8", - "imp": "unicode/1f47f.png?v8", - "inbox_tray": "unicode/1f4e5.png?v8", - "incoming_envelope": "unicode/1f4e8.png?v8", - "india": "unicode/1f1ee-1f1f3.png?v8", - "indonesia": "unicode/1f1ee-1f1e9.png?v8", - "infinity": "unicode/267e.png?v8", - "information_desk_person": "unicode/1f481.png?v8", - "information_source": "unicode/2139.png?v8", - "innocent": "unicode/1f607.png?v8", - "interrobang": "unicode/2049.png?v8", - "iphone": "unicode/1f4f1.png?v8", - "iran": "unicode/1f1ee-1f1f7.png?v8", - "iraq": "unicode/1f1ee-1f1f6.png?v8", - "ireland": "unicode/1f1ee-1f1ea.png?v8", - "isle_of_man": "unicode/1f1ee-1f1f2.png?v8", - "israel": "unicode/1f1ee-1f1f1.png?v8", - "it": "unicode/1f1ee-1f1f9.png?v8", - "izakaya_lantern": "unicode/1f3ee.png?v8", - "jack_o_lantern": "unicode/1f383.png?v8", - "jamaica": "unicode/1f1ef-1f1f2.png?v8", - "japan": "unicode/1f5fe.png?v8", - "japanese_castle": "unicode/1f3ef.png?v8", - "japanese_goblin": "unicode/1f47a.png?v8", - "japanese_ogre": "unicode/1f479.png?v8", - "jeans": "unicode/1f456.png?v8", - "jersey": "unicode/1f1ef-1f1ea.png?v8", - "jigsaw": "unicode/1f9e9.png?v8", - "jordan": "unicode/1f1ef-1f1f4.png?v8", - "joy": "unicode/1f602.png?v8", - "joy_cat": "unicode/1f639.png?v8", - "joystick": "unicode/1f579.png?v8", - "jp": "unicode/1f1ef-1f1f5.png?v8", - "judge": "unicode/1f9d1-2696.png?v8", - "juggling_person": "unicode/1f939.png?v8", - "kaaba": "unicode/1f54b.png?v8", - "kangaroo": "unicode/1f998.png?v8", - "kazakhstan": "unicode/1f1f0-1f1ff.png?v8", - "kenya": "unicode/1f1f0-1f1ea.png?v8", - "key": "unicode/1f511.png?v8", - "keyboard": "unicode/2328.png?v8", - "keycap_ten": "unicode/1f51f.png?v8", - "kick_scooter": "unicode/1f6f4.png?v8", - "kimono": "unicode/1f458.png?v8", - "kiribati": "unicode/1f1f0-1f1ee.png?v8", - "kiss": "unicode/1f48b.png?v8", - "kissing": "unicode/1f617.png?v8", - "kissing_cat": "unicode/1f63d.png?v8", - "kissing_closed_eyes": "unicode/1f61a.png?v8", - "kissing_heart": "unicode/1f618.png?v8", - "kissing_smiling_eyes": "unicode/1f619.png?v8", - "kite": "unicode/1fa81.png?v8", - "kiwi_fruit": "unicode/1f95d.png?v8", - "kneeling_man": "unicode/1f9ce-2642.png?v8", - "kneeling_person": "unicode/1f9ce.png?v8", - "kneeling_woman": "unicode/1f9ce-2640.png?v8", - "knife": "unicode/1f52a.png?v8", - "knot": "unicode/1faa2.png?v8", - "koala": "unicode/1f428.png?v8", - "koko": "unicode/1f201.png?v8", - "kosovo": "unicode/1f1fd-1f1f0.png?v8", - "kr": "unicode/1f1f0-1f1f7.png?v8", - "kuwait": "unicode/1f1f0-1f1fc.png?v8", - "kyrgyzstan": "unicode/1f1f0-1f1ec.png?v8", - "lab_coat": "unicode/1f97c.png?v8", - "label": "unicode/1f3f7.png?v8", - "lacrosse": "unicode/1f94d.png?v8", - "ladder": "unicode/1fa9c.png?v8", - "lady_beetle": "unicode/1f41e.png?v8", - "lantern": "unicode/1f3ee.png?v8", - "laos": "unicode/1f1f1-1f1e6.png?v8", - "large_blue_circle": "unicode/1f535.png?v8", - "large_blue_diamond": "unicode/1f537.png?v8", - "large_orange_diamond": "unicode/1f536.png?v8", - "last_quarter_moon": "unicode/1f317.png?v8", - "last_quarter_moon_with_face": "unicode/1f31c.png?v8", - "latin_cross": "unicode/271d.png?v8", - "latvia": "unicode/1f1f1-1f1fb.png?v8", - "laughing": "unicode/1f606.png?v8", - "leafy_green": "unicode/1f96c.png?v8", - "leaves": "unicode/1f343.png?v8", - "lebanon": "unicode/1f1f1-1f1e7.png?v8", - "ledger": "unicode/1f4d2.png?v8", - "left_luggage": "unicode/1f6c5.png?v8", - "left_right_arrow": "unicode/2194.png?v8", - "left_speech_bubble": "unicode/1f5e8.png?v8", - "leftwards_arrow_with_hook": "unicode/21a9.png?v8", - "leg": "unicode/1f9b5.png?v8", - "lemon": "unicode/1f34b.png?v8", - "leo": "unicode/264c.png?v8", - "leopard": "unicode/1f406.png?v8", - "lesotho": "unicode/1f1f1-1f1f8.png?v8", - "level_slider": "unicode/1f39a.png?v8", - "liberia": "unicode/1f1f1-1f1f7.png?v8", - "libra": "unicode/264e.png?v8", - "libya": "unicode/1f1f1-1f1fe.png?v8", - "liechtenstein": "unicode/1f1f1-1f1ee.png?v8", - "light_rail": "unicode/1f688.png?v8", - "link": "unicode/1f517.png?v8", - "lion": "unicode/1f981.png?v8", - "lips": "unicode/1f444.png?v8", - "lipstick": "unicode/1f484.png?v8", - "lithuania": "unicode/1f1f1-1f1f9.png?v8", - "lizard": "unicode/1f98e.png?v8", - "llama": "unicode/1f999.png?v8", - "lobster": "unicode/1f99e.png?v8", - "lock": "unicode/1f512.png?v8", - "lock_with_ink_pen": "unicode/1f50f.png?v8", - "lollipop": "unicode/1f36d.png?v8", - "long_drum": "unicode/1fa98.png?v8", - "loop": "unicode/27bf.png?v8", - "lotion_bottle": "unicode/1f9f4.png?v8", - "lotus_position": "unicode/1f9d8.png?v8", - "lotus_position_man": "unicode/1f9d8-2642.png?v8", - "lotus_position_woman": "unicode/1f9d8-2640.png?v8", - "loud_sound": "unicode/1f50a.png?v8", - "loudspeaker": "unicode/1f4e2.png?v8", - "love_hotel": "unicode/1f3e9.png?v8", - "love_letter": "unicode/1f48c.png?v8", - "love_you_gesture": "unicode/1f91f.png?v8", - "low_brightness": "unicode/1f505.png?v8", - "luggage": "unicode/1f9f3.png?v8", - "lungs": "unicode/1fac1.png?v8", - "luxembourg": "unicode/1f1f1-1f1fa.png?v8", - "lying_face": "unicode/1f925.png?v8", - "m": "unicode/24c2.png?v8", - "macau": "unicode/1f1f2-1f1f4.png?v8", - "macedonia": "unicode/1f1f2-1f1f0.png?v8", - "madagascar": "unicode/1f1f2-1f1ec.png?v8", - "mag": "unicode/1f50d.png?v8", - "mag_right": "unicode/1f50e.png?v8", - "mage": "unicode/1f9d9.png?v8", - "mage_man": "unicode/1f9d9-2642.png?v8", - "mage_woman": "unicode/1f9d9-2640.png?v8", - "magic_wand": "unicode/1fa84.png?v8", - "magnet": "unicode/1f9f2.png?v8", - "mahjong": "unicode/1f004.png?v8", - "mailbox": "unicode/1f4eb.png?v8", - "mailbox_closed": "unicode/1f4ea.png?v8", - "mailbox_with_mail": "unicode/1f4ec.png?v8", - "mailbox_with_no_mail": "unicode/1f4ed.png?v8", - "malawi": "unicode/1f1f2-1f1fc.png?v8", - "malaysia": "unicode/1f1f2-1f1fe.png?v8", - "maldives": "unicode/1f1f2-1f1fb.png?v8", - "male_detective": "unicode/1f575-2642.png?v8", - "male_sign": "unicode/2642.png?v8", - "mali": "unicode/1f1f2-1f1f1.png?v8", - "malta": "unicode/1f1f2-1f1f9.png?v8", - "mammoth": "unicode/1f9a3.png?v8", - "man": "unicode/1f468.png?v8", - "man_artist": "unicode/1f468-1f3a8.png?v8", - "man_astronaut": "unicode/1f468-1f680.png?v8", - "man_beard": "unicode/1f9d4-2642.png?v8", - "man_cartwheeling": "unicode/1f938-2642.png?v8", - "man_cook": "unicode/1f468-1f373.png?v8", - "man_dancing": "unicode/1f57a.png?v8", - "man_facepalming": "unicode/1f926-2642.png?v8", - "man_factory_worker": "unicode/1f468-1f3ed.png?v8", - "man_farmer": "unicode/1f468-1f33e.png?v8", - "man_feeding_baby": "unicode/1f468-1f37c.png?v8", - "man_firefighter": "unicode/1f468-1f692.png?v8", - "man_health_worker": "unicode/1f468-2695.png?v8", - "man_in_manual_wheelchair": "unicode/1f468-1f9bd.png?v8", - "man_in_motorized_wheelchair": "unicode/1f468-1f9bc.png?v8", - "man_in_tuxedo": "unicode/1f935-2642.png?v8", - "man_judge": "unicode/1f468-2696.png?v8", - "man_juggling": "unicode/1f939-2642.png?v8", - "man_mechanic": "unicode/1f468-1f527.png?v8", - "man_office_worker": "unicode/1f468-1f4bc.png?v8", - "man_pilot": "unicode/1f468-2708.png?v8", - "man_playing_handball": "unicode/1f93e-2642.png?v8", - "man_playing_water_polo": "unicode/1f93d-2642.png?v8", - "man_scientist": "unicode/1f468-1f52c.png?v8", - "man_shrugging": "unicode/1f937-2642.png?v8", - "man_singer": "unicode/1f468-1f3a4.png?v8", - "man_student": "unicode/1f468-1f393.png?v8", - "man_teacher": "unicode/1f468-1f3eb.png?v8", - "man_technologist": "unicode/1f468-1f4bb.png?v8", - "man_with_gua_pi_mao": "unicode/1f472.png?v8", - "man_with_probing_cane": "unicode/1f468-1f9af.png?v8", - "man_with_turban": "unicode/1f473-2642.png?v8", - "man_with_veil": "unicode/1f470-2642.png?v8", - "mandarin": "unicode/1f34a.png?v8", - "mango": "unicode/1f96d.png?v8", - "mans_shoe": "unicode/1f45e.png?v8", - "mantelpiece_clock": "unicode/1f570.png?v8", - "manual_wheelchair": "unicode/1f9bd.png?v8", - "maple_leaf": "unicode/1f341.png?v8", - "marshall_islands": "unicode/1f1f2-1f1ed.png?v8", - "martial_arts_uniform": "unicode/1f94b.png?v8", - "martinique": "unicode/1f1f2-1f1f6.png?v8", - "mask": "unicode/1f637.png?v8", - "massage": "unicode/1f486.png?v8", - "massage_man": "unicode/1f486-2642.png?v8", - "massage_woman": "unicode/1f486-2640.png?v8", - "mate": "unicode/1f9c9.png?v8", - "mauritania": "unicode/1f1f2-1f1f7.png?v8", - "mauritius": "unicode/1f1f2-1f1fa.png?v8", - "mayotte": "unicode/1f1fe-1f1f9.png?v8", - "meat_on_bone": "unicode/1f356.png?v8", - "mechanic": "unicode/1f9d1-1f527.png?v8", - "mechanical_arm": "unicode/1f9be.png?v8", - "mechanical_leg": "unicode/1f9bf.png?v8", - "medal_military": "unicode/1f396.png?v8", - "medal_sports": "unicode/1f3c5.png?v8", - "medical_symbol": "unicode/2695.png?v8", - "mega": "unicode/1f4e3.png?v8", - "melon": "unicode/1f348.png?v8", - "memo": "unicode/1f4dd.png?v8", - "men_wrestling": "unicode/1f93c-2642.png?v8", - "mending_heart": "unicode/2764-1fa79.png?v8", - "menorah": "unicode/1f54e.png?v8", - "mens": "unicode/1f6b9.png?v8", - "mermaid": "unicode/1f9dc-2640.png?v8", - "merman": "unicode/1f9dc-2642.png?v8", - "merperson": "unicode/1f9dc.png?v8", - "metal": "unicode/1f918.png?v8", - "metro": "unicode/1f687.png?v8", - "mexico": "unicode/1f1f2-1f1fd.png?v8", - "microbe": "unicode/1f9a0.png?v8", - "micronesia": "unicode/1f1eb-1f1f2.png?v8", - "microphone": "unicode/1f3a4.png?v8", - "microscope": "unicode/1f52c.png?v8", - "middle_finger": "unicode/1f595.png?v8", - "military_helmet": "unicode/1fa96.png?v8", - "milk_glass": "unicode/1f95b.png?v8", - "milky_way": "unicode/1f30c.png?v8", - "minibus": "unicode/1f690.png?v8", - "minidisc": "unicode/1f4bd.png?v8", - "mirror": "unicode/1fa9e.png?v8", - "mobile_phone_off": "unicode/1f4f4.png?v8", - "moldova": "unicode/1f1f2-1f1e9.png?v8", - "monaco": "unicode/1f1f2-1f1e8.png?v8", - "money_mouth_face": "unicode/1f911.png?v8", - "money_with_wings": "unicode/1f4b8.png?v8", - "moneybag": "unicode/1f4b0.png?v8", - "mongolia": "unicode/1f1f2-1f1f3.png?v8", - "monkey": "unicode/1f412.png?v8", - "monkey_face": "unicode/1f435.png?v8", - "monocle_face": "unicode/1f9d0.png?v8", - "monorail": "unicode/1f69d.png?v8", - "montenegro": "unicode/1f1f2-1f1ea.png?v8", - "montserrat": "unicode/1f1f2-1f1f8.png?v8", - "moon": "unicode/1f314.png?v8", - "moon_cake": "unicode/1f96e.png?v8", - "morocco": "unicode/1f1f2-1f1e6.png?v8", - "mortar_board": "unicode/1f393.png?v8", - "mosque": "unicode/1f54c.png?v8", - "mosquito": "unicode/1f99f.png?v8", - "motor_boat": "unicode/1f6e5.png?v8", - "motor_scooter": "unicode/1f6f5.png?v8", - "motorcycle": "unicode/1f3cd.png?v8", - "motorized_wheelchair": "unicode/1f9bc.png?v8", - "motorway": "unicode/1f6e3.png?v8", - "mount_fuji": "unicode/1f5fb.png?v8", - "mountain": "unicode/26f0.png?v8", - "mountain_bicyclist": "unicode/1f6b5.png?v8", - "mountain_biking_man": "unicode/1f6b5-2642.png?v8", - "mountain_biking_woman": "unicode/1f6b5-2640.png?v8", - "mountain_cableway": "unicode/1f6a0.png?v8", - "mountain_railway": "unicode/1f69e.png?v8", - "mountain_snow": "unicode/1f3d4.png?v8", - "mouse": "unicode/1f42d.png?v8", - "mouse2": "unicode/1f401.png?v8", - "mouse_trap": "unicode/1faa4.png?v8", - "movie_camera": "unicode/1f3a5.png?v8", - "moyai": "unicode/1f5ff.png?v8", - "mozambique": "unicode/1f1f2-1f1ff.png?v8", - "mrs_claus": "unicode/1f936.png?v8", - "muscle": "unicode/1f4aa.png?v8", - "mushroom": "unicode/1f344.png?v8", - "musical_keyboard": "unicode/1f3b9.png?v8", - "musical_note": "unicode/1f3b5.png?v8", - "musical_score": "unicode/1f3bc.png?v8", - "mute": "unicode/1f507.png?v8", - "mx_claus": "unicode/1f9d1-1f384.png?v8", - "myanmar": "unicode/1f1f2-1f1f2.png?v8", - "nail_care": "unicode/1f485.png?v8", - "name_badge": "unicode/1f4db.png?v8", - "namibia": "unicode/1f1f3-1f1e6.png?v8", - "national_park": "unicode/1f3de.png?v8", - "nauru": "unicode/1f1f3-1f1f7.png?v8", - "nauseated_face": "unicode/1f922.png?v8", - "nazar_amulet": "unicode/1f9ff.png?v8", - "neckbeard": "neckbeard.png?v8", - "necktie": "unicode/1f454.png?v8", - "negative_squared_cross_mark": "unicode/274e.png?v8", - "nepal": "unicode/1f1f3-1f1f5.png?v8", - "nerd_face": "unicode/1f913.png?v8", - "nesting_dolls": "unicode/1fa86.png?v8", - "netherlands": "unicode/1f1f3-1f1f1.png?v8", - "neutral_face": "unicode/1f610.png?v8", - "new": "unicode/1f195.png?v8", - "new_caledonia": "unicode/1f1f3-1f1e8.png?v8", - "new_moon": "unicode/1f311.png?v8", - "new_moon_with_face": "unicode/1f31a.png?v8", - "new_zealand": "unicode/1f1f3-1f1ff.png?v8", - "newspaper": "unicode/1f4f0.png?v8", - "newspaper_roll": "unicode/1f5de.png?v8", - "next_track_button": "unicode/23ed.png?v8", - "ng": "unicode/1f196.png?v8", - "ng_man": "unicode/1f645-2642.png?v8", - "ng_woman": "unicode/1f645-2640.png?v8", - "nicaragua": "unicode/1f1f3-1f1ee.png?v8", - "niger": "unicode/1f1f3-1f1ea.png?v8", - "nigeria": "unicode/1f1f3-1f1ec.png?v8", - "night_with_stars": "unicode/1f303.png?v8", - "nine": "unicode/0039-20e3.png?v8", - "ninja": "unicode/1f977.png?v8", - "niue": "unicode/1f1f3-1f1fa.png?v8", - "no_bell": "unicode/1f515.png?v8", - "no_bicycles": "unicode/1f6b3.png?v8", - "no_entry": "unicode/26d4.png?v8", - "no_entry_sign": "unicode/1f6ab.png?v8", - "no_good": "unicode/1f645.png?v8", - "no_good_man": "unicode/1f645-2642.png?v8", - "no_good_woman": "unicode/1f645-2640.png?v8", - "no_mobile_phones": "unicode/1f4f5.png?v8", - "no_mouth": "unicode/1f636.png?v8", - "no_pedestrians": "unicode/1f6b7.png?v8", - "no_smoking": "unicode/1f6ad.png?v8", - "non-potable_water": "unicode/1f6b1.png?v8", - "norfolk_island": "unicode/1f1f3-1f1eb.png?v8", - "north_korea": "unicode/1f1f0-1f1f5.png?v8", - "northern_mariana_islands": "unicode/1f1f2-1f1f5.png?v8", - "norway": "unicode/1f1f3-1f1f4.png?v8", - "nose": "unicode/1f443.png?v8", - "notebook": "unicode/1f4d3.png?v8", - "notebook_with_decorative_cover": "unicode/1f4d4.png?v8", - "notes": "unicode/1f3b6.png?v8", - "nut_and_bolt": "unicode/1f529.png?v8", - "o": "unicode/2b55.png?v8", - "o2": "unicode/1f17e.png?v8", - "ocean": "unicode/1f30a.png?v8", - "octocat": "octocat.png?v8", - "octopus": "unicode/1f419.png?v8", - "oden": "unicode/1f362.png?v8", - "office": "unicode/1f3e2.png?v8", - "office_worker": "unicode/1f9d1-1f4bc.png?v8", - "oil_drum": "unicode/1f6e2.png?v8", - "ok": "unicode/1f197.png?v8", - "ok_hand": "unicode/1f44c.png?v8", - "ok_man": "unicode/1f646-2642.png?v8", - "ok_person": "unicode/1f646.png?v8", - "ok_woman": "unicode/1f646-2640.png?v8", - "old_key": "unicode/1f5dd.png?v8", - "older_adult": "unicode/1f9d3.png?v8", - "older_man": "unicode/1f474.png?v8", - "older_woman": "unicode/1f475.png?v8", - "olive": "unicode/1fad2.png?v8", - "om": "unicode/1f549.png?v8", - "oman": "unicode/1f1f4-1f1f2.png?v8", - "on": "unicode/1f51b.png?v8", - "oncoming_automobile": "unicode/1f698.png?v8", - "oncoming_bus": "unicode/1f68d.png?v8", - "oncoming_police_car": "unicode/1f694.png?v8", - "oncoming_taxi": "unicode/1f696.png?v8", - "one": "unicode/0031-20e3.png?v8", - "one_piece_swimsuit": "unicode/1fa71.png?v8", - "onion": "unicode/1f9c5.png?v8", - "open_book": "unicode/1f4d6.png?v8", - "open_file_folder": "unicode/1f4c2.png?v8", - "open_hands": "unicode/1f450.png?v8", - "open_mouth": "unicode/1f62e.png?v8", - "open_umbrella": "unicode/2602.png?v8", - "ophiuchus": "unicode/26ce.png?v8", - "orange": "unicode/1f34a.png?v8", - "orange_book": "unicode/1f4d9.png?v8", - "orange_circle": "unicode/1f7e0.png?v8", - "orange_heart": "unicode/1f9e1.png?v8", - "orange_square": "unicode/1f7e7.png?v8", - "orangutan": "unicode/1f9a7.png?v8", - "orthodox_cross": "unicode/2626.png?v8", - "otter": "unicode/1f9a6.png?v8", - "outbox_tray": "unicode/1f4e4.png?v8", - "owl": "unicode/1f989.png?v8", - "ox": "unicode/1f402.png?v8", - "oyster": "unicode/1f9aa.png?v8", - "package": "unicode/1f4e6.png?v8", - "page_facing_up": "unicode/1f4c4.png?v8", - "page_with_curl": "unicode/1f4c3.png?v8", - "pager": "unicode/1f4df.png?v8", - "paintbrush": "unicode/1f58c.png?v8", - "pakistan": "unicode/1f1f5-1f1f0.png?v8", - "palau": "unicode/1f1f5-1f1fc.png?v8", - "palestinian_territories": "unicode/1f1f5-1f1f8.png?v8", - "palm_tree": "unicode/1f334.png?v8", - "palms_up_together": "unicode/1f932.png?v8", - "panama": "unicode/1f1f5-1f1e6.png?v8", - "pancakes": "unicode/1f95e.png?v8", - "panda_face": "unicode/1f43c.png?v8", - "paperclip": "unicode/1f4ce.png?v8", - "paperclips": "unicode/1f587.png?v8", - "papua_new_guinea": "unicode/1f1f5-1f1ec.png?v8", - "parachute": "unicode/1fa82.png?v8", - "paraguay": "unicode/1f1f5-1f1fe.png?v8", - "parasol_on_ground": "unicode/26f1.png?v8", - "parking": "unicode/1f17f.png?v8", - "parrot": "unicode/1f99c.png?v8", - "part_alternation_mark": "unicode/303d.png?v8", - "partly_sunny": "unicode/26c5.png?v8", - "partying_face": "unicode/1f973.png?v8", - "passenger_ship": "unicode/1f6f3.png?v8", - "passport_control": "unicode/1f6c2.png?v8", - "pause_button": "unicode/23f8.png?v8", - "paw_prints": "unicode/1f43e.png?v8", - "peace_symbol": "unicode/262e.png?v8", - "peach": "unicode/1f351.png?v8", - "peacock": "unicode/1f99a.png?v8", - "peanuts": "unicode/1f95c.png?v8", - "pear": "unicode/1f350.png?v8", - "pen": "unicode/1f58a.png?v8", - "pencil": "unicode/1f4dd.png?v8", - "pencil2": "unicode/270f.png?v8", - "penguin": "unicode/1f427.png?v8", - "pensive": "unicode/1f614.png?v8", - "people_holding_hands": "unicode/1f9d1-1f91d-1f9d1.png?v8", - "people_hugging": "unicode/1fac2.png?v8", - "performing_arts": "unicode/1f3ad.png?v8", - "persevere": "unicode/1f623.png?v8", - "person_bald": "unicode/1f9d1-1f9b2.png?v8", - "person_curly_hair": "unicode/1f9d1-1f9b1.png?v8", - "person_feeding_baby": "unicode/1f9d1-1f37c.png?v8", - "person_fencing": "unicode/1f93a.png?v8", - "person_in_manual_wheelchair": "unicode/1f9d1-1f9bd.png?v8", - "person_in_motorized_wheelchair": "unicode/1f9d1-1f9bc.png?v8", - "person_in_tuxedo": "unicode/1f935.png?v8", - "person_red_hair": "unicode/1f9d1-1f9b0.png?v8", - "person_white_hair": "unicode/1f9d1-1f9b3.png?v8", - "person_with_probing_cane": "unicode/1f9d1-1f9af.png?v8", - "person_with_turban": "unicode/1f473.png?v8", - "person_with_veil": "unicode/1f470.png?v8", - "peru": "unicode/1f1f5-1f1ea.png?v8", - "petri_dish": "unicode/1f9eb.png?v8", - "philippines": "unicode/1f1f5-1f1ed.png?v8", - "phone": "unicode/260e.png?v8", - "pick": "unicode/26cf.png?v8", - "pickup_truck": "unicode/1f6fb.png?v8", - "pie": "unicode/1f967.png?v8", - "pig": "unicode/1f437.png?v8", - "pig2": "unicode/1f416.png?v8", - "pig_nose": "unicode/1f43d.png?v8", - "pill": "unicode/1f48a.png?v8", - "pilot": "unicode/1f9d1-2708.png?v8", - "pinata": "unicode/1fa85.png?v8", - "pinched_fingers": "unicode/1f90c.png?v8", - "pinching_hand": "unicode/1f90f.png?v8", - "pineapple": "unicode/1f34d.png?v8", - "ping_pong": "unicode/1f3d3.png?v8", - "pirate_flag": "unicode/1f3f4-2620.png?v8", - "pisces": "unicode/2653.png?v8", - "pitcairn_islands": "unicode/1f1f5-1f1f3.png?v8", - "pizza": "unicode/1f355.png?v8", - "placard": "unicode/1faa7.png?v8", - "place_of_worship": "unicode/1f6d0.png?v8", - "plate_with_cutlery": "unicode/1f37d.png?v8", - "play_or_pause_button": "unicode/23ef.png?v8", - "pleading_face": "unicode/1f97a.png?v8", - "plunger": "unicode/1faa0.png?v8", - "point_down": "unicode/1f447.png?v8", - "point_left": "unicode/1f448.png?v8", - "point_right": "unicode/1f449.png?v8", - "point_up": "unicode/261d.png?v8", - "point_up_2": "unicode/1f446.png?v8", - "poland": "unicode/1f1f5-1f1f1.png?v8", - "polar_bear": "unicode/1f43b-2744.png?v8", - "police_car": "unicode/1f693.png?v8", - "police_officer": "unicode/1f46e.png?v8", - "policeman": "unicode/1f46e-2642.png?v8", - "policewoman": "unicode/1f46e-2640.png?v8", - "poodle": "unicode/1f429.png?v8", - "poop": "unicode/1f4a9.png?v8", - "popcorn": "unicode/1f37f.png?v8", - "portugal": "unicode/1f1f5-1f1f9.png?v8", - "post_office": "unicode/1f3e3.png?v8", - "postal_horn": "unicode/1f4ef.png?v8", - "postbox": "unicode/1f4ee.png?v8", - "potable_water": "unicode/1f6b0.png?v8", - "potato": "unicode/1f954.png?v8", - "potted_plant": "unicode/1fab4.png?v8", - "pouch": "unicode/1f45d.png?v8", - "poultry_leg": "unicode/1f357.png?v8", - "pound": "unicode/1f4b7.png?v8", - "pout": "unicode/1f621.png?v8", - "pouting_cat": "unicode/1f63e.png?v8", - "pouting_face": "unicode/1f64e.png?v8", - "pouting_man": "unicode/1f64e-2642.png?v8", - "pouting_woman": "unicode/1f64e-2640.png?v8", - "pray": "unicode/1f64f.png?v8", - "prayer_beads": "unicode/1f4ff.png?v8", - "pregnant_woman": "unicode/1f930.png?v8", - "pretzel": "unicode/1f968.png?v8", - "previous_track_button": "unicode/23ee.png?v8", - "prince": "unicode/1f934.png?v8", - "princess": "unicode/1f478.png?v8", - "printer": "unicode/1f5a8.png?v8", - "probing_cane": "unicode/1f9af.png?v8", - "puerto_rico": "unicode/1f1f5-1f1f7.png?v8", - "punch": "unicode/1f44a.png?v8", - "purple_circle": "unicode/1f7e3.png?v8", - "purple_heart": "unicode/1f49c.png?v8", - "purple_square": "unicode/1f7ea.png?v8", - "purse": "unicode/1f45b.png?v8", - "pushpin": "unicode/1f4cc.png?v8", - "put_litter_in_its_place": "unicode/1f6ae.png?v8", - "qatar": "unicode/1f1f6-1f1e6.png?v8", - "question": "unicode/2753.png?v8", - "rabbit": "unicode/1f430.png?v8", - "rabbit2": "unicode/1f407.png?v8", - "raccoon": "unicode/1f99d.png?v8", - "racehorse": "unicode/1f40e.png?v8", - "racing_car": "unicode/1f3ce.png?v8", - "radio": "unicode/1f4fb.png?v8", - "radio_button": "unicode/1f518.png?v8", - "radioactive": "unicode/2622.png?v8", - "rage": "unicode/1f621.png?v8", - "rage1": "rage1.png?v8", - "rage2": "rage2.png?v8", - "rage3": "rage3.png?v8", - "rage4": "rage4.png?v8", - "railway_car": "unicode/1f683.png?v8", - "railway_track": "unicode/1f6e4.png?v8", - "rainbow": "unicode/1f308.png?v8", - "rainbow_flag": "unicode/1f3f3-1f308.png?v8", - "raised_back_of_hand": "unicode/1f91a.png?v8", - "raised_eyebrow": "unicode/1f928.png?v8", - "raised_hand": "unicode/270b.png?v8", - "raised_hand_with_fingers_splayed": "unicode/1f590.png?v8", - "raised_hands": "unicode/1f64c.png?v8", - "raising_hand": "unicode/1f64b.png?v8", - "raising_hand_man": "unicode/1f64b-2642.png?v8", - "raising_hand_woman": "unicode/1f64b-2640.png?v8", - "ram": "unicode/1f40f.png?v8", - "ramen": "unicode/1f35c.png?v8", - "rat": "unicode/1f400.png?v8", - "razor": "unicode/1fa92.png?v8", - "receipt": "unicode/1f9fe.png?v8", - "record_button": "unicode/23fa.png?v8", - "recycle": "unicode/267b.png?v8", - "red_car": "unicode/1f697.png?v8", - "red_circle": "unicode/1f534.png?v8", - "red_envelope": "unicode/1f9e7.png?v8", - "red_haired_man": "unicode/1f468-1f9b0.png?v8", - "red_haired_woman": "unicode/1f469-1f9b0.png?v8", - "red_square": "unicode/1f7e5.png?v8", - "registered": "unicode/00ae.png?v8", - "relaxed": "unicode/263a.png?v8", - "relieved": "unicode/1f60c.png?v8", - "reminder_ribbon": "unicode/1f397.png?v8", - "repeat": "unicode/1f501.png?v8", - "repeat_one": "unicode/1f502.png?v8", - "rescue_worker_helmet": "unicode/26d1.png?v8", - "restroom": "unicode/1f6bb.png?v8", - "reunion": "unicode/1f1f7-1f1ea.png?v8", - "revolving_hearts": "unicode/1f49e.png?v8", - "rewind": "unicode/23ea.png?v8", - "rhinoceros": "unicode/1f98f.png?v8", - "ribbon": "unicode/1f380.png?v8", - "rice": "unicode/1f35a.png?v8", - "rice_ball": "unicode/1f359.png?v8", - "rice_cracker": "unicode/1f358.png?v8", - "rice_scene": "unicode/1f391.png?v8", - "right_anger_bubble": "unicode/1f5ef.png?v8", - "ring": "unicode/1f48d.png?v8", - "ringed_planet": "unicode/1fa90.png?v8", - "robot": "unicode/1f916.png?v8", - "rock": "unicode/1faa8.png?v8", - "rocket": "unicode/1f680.png?v8", - "rofl": "unicode/1f923.png?v8", - "roll_eyes": "unicode/1f644.png?v8", - "roll_of_paper": "unicode/1f9fb.png?v8", - "roller_coaster": "unicode/1f3a2.png?v8", - "roller_skate": "unicode/1f6fc.png?v8", - "romania": "unicode/1f1f7-1f1f4.png?v8", - "rooster": "unicode/1f413.png?v8", - "rose": "unicode/1f339.png?v8", - "rosette": "unicode/1f3f5.png?v8", - "rotating_light": "unicode/1f6a8.png?v8", - "round_pushpin": "unicode/1f4cd.png?v8", - "rowboat": "unicode/1f6a3.png?v8", - "rowing_man": "unicode/1f6a3-2642.png?v8", - "rowing_woman": "unicode/1f6a3-2640.png?v8", - "ru": "unicode/1f1f7-1f1fa.png?v8", - "rugby_football": "unicode/1f3c9.png?v8", - "runner": "unicode/1f3c3.png?v8", - "running": "unicode/1f3c3.png?v8", - "running_man": "unicode/1f3c3-2642.png?v8", - "running_shirt_with_sash": "unicode/1f3bd.png?v8", - "running_woman": "unicode/1f3c3-2640.png?v8", - "rwanda": "unicode/1f1f7-1f1fc.png?v8", - "sa": "unicode/1f202.png?v8", - "safety_pin": "unicode/1f9f7.png?v8", - "safety_vest": "unicode/1f9ba.png?v8", - "sagittarius": "unicode/2650.png?v8", - "sailboat": "unicode/26f5.png?v8", - "sake": "unicode/1f376.png?v8", - "salt": "unicode/1f9c2.png?v8", - "samoa": "unicode/1f1fc-1f1f8.png?v8", - "san_marino": "unicode/1f1f8-1f1f2.png?v8", - "sandal": "unicode/1f461.png?v8", - "sandwich": "unicode/1f96a.png?v8", - "santa": "unicode/1f385.png?v8", - "sao_tome_principe": "unicode/1f1f8-1f1f9.png?v8", - "sari": "unicode/1f97b.png?v8", - "sassy_man": "unicode/1f481-2642.png?v8", - "sassy_woman": "unicode/1f481-2640.png?v8", - "satellite": "unicode/1f4e1.png?v8", - "satisfied": "unicode/1f606.png?v8", - "saudi_arabia": "unicode/1f1f8-1f1e6.png?v8", - "sauna_man": "unicode/1f9d6-2642.png?v8", - "sauna_person": "unicode/1f9d6.png?v8", - "sauna_woman": "unicode/1f9d6-2640.png?v8", - "sauropod": "unicode/1f995.png?v8", - "saxophone": "unicode/1f3b7.png?v8", - "scarf": "unicode/1f9e3.png?v8", - "school": "unicode/1f3eb.png?v8", - "school_satchel": "unicode/1f392.png?v8", - "scientist": "unicode/1f9d1-1f52c.png?v8", - "scissors": "unicode/2702.png?v8", - "scorpion": "unicode/1f982.png?v8", - "scorpius": "unicode/264f.png?v8", - "scotland": "unicode/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f.png?v8", - "scream": "unicode/1f631.png?v8", - "scream_cat": "unicode/1f640.png?v8", - "screwdriver": "unicode/1fa9b.png?v8", - "scroll": "unicode/1f4dc.png?v8", - "seal": "unicode/1f9ad.png?v8", - "seat": "unicode/1f4ba.png?v8", - "secret": "unicode/3299.png?v8", - "see_no_evil": "unicode/1f648.png?v8", - "seedling": "unicode/1f331.png?v8", - "selfie": "unicode/1f933.png?v8", - "senegal": "unicode/1f1f8-1f1f3.png?v8", - "serbia": "unicode/1f1f7-1f1f8.png?v8", - "service_dog": "unicode/1f415-1f9ba.png?v8", - "seven": "unicode/0037-20e3.png?v8", - "sewing_needle": "unicode/1faa1.png?v8", - "seychelles": "unicode/1f1f8-1f1e8.png?v8", - "shallow_pan_of_food": "unicode/1f958.png?v8", - "shamrock": "unicode/2618.png?v8", - "shark": "unicode/1f988.png?v8", - "shaved_ice": "unicode/1f367.png?v8", - "sheep": "unicode/1f411.png?v8", - "shell": "unicode/1f41a.png?v8", - "shield": "unicode/1f6e1.png?v8", - "shinto_shrine": "unicode/26e9.png?v8", - "ship": "unicode/1f6a2.png?v8", - "shipit": "shipit.png?v8", - "shirt": "unicode/1f455.png?v8", - "shit": "unicode/1f4a9.png?v8", - "shoe": "unicode/1f45e.png?v8", - "shopping": "unicode/1f6cd.png?v8", - "shopping_cart": "unicode/1f6d2.png?v8", - "shorts": "unicode/1fa73.png?v8", - "shower": "unicode/1f6bf.png?v8", - "shrimp": "unicode/1f990.png?v8", - "shrug": "unicode/1f937.png?v8", - "shushing_face": "unicode/1f92b.png?v8", - "sierra_leone": "unicode/1f1f8-1f1f1.png?v8", - "signal_strength": "unicode/1f4f6.png?v8", - "singapore": "unicode/1f1f8-1f1ec.png?v8", - "singer": "unicode/1f9d1-1f3a4.png?v8", - "sint_maarten": "unicode/1f1f8-1f1fd.png?v8", - "six": "unicode/0036-20e3.png?v8", - "six_pointed_star": "unicode/1f52f.png?v8", - "skateboard": "unicode/1f6f9.png?v8", - "ski": "unicode/1f3bf.png?v8", - "skier": "unicode/26f7.png?v8", - "skull": "unicode/1f480.png?v8", - "skull_and_crossbones": "unicode/2620.png?v8", - "skunk": "unicode/1f9a8.png?v8", - "sled": "unicode/1f6f7.png?v8", - "sleeping": "unicode/1f634.png?v8", - "sleeping_bed": "unicode/1f6cc.png?v8", - "sleepy": "unicode/1f62a.png?v8", - "slightly_frowning_face": "unicode/1f641.png?v8", - "slightly_smiling_face": "unicode/1f642.png?v8", - "slot_machine": "unicode/1f3b0.png?v8", - "sloth": "unicode/1f9a5.png?v8", - "slovakia": "unicode/1f1f8-1f1f0.png?v8", - "slovenia": "unicode/1f1f8-1f1ee.png?v8", - "small_airplane": "unicode/1f6e9.png?v8", - "small_blue_diamond": "unicode/1f539.png?v8", - "small_orange_diamond": "unicode/1f538.png?v8", - "small_red_triangle": "unicode/1f53a.png?v8", - "small_red_triangle_down": "unicode/1f53b.png?v8", - "smile": "unicode/1f604.png?v8", - "smile_cat": "unicode/1f638.png?v8", - "smiley": "unicode/1f603.png?v8", - "smiley_cat": "unicode/1f63a.png?v8", - "smiling_face_with_tear": "unicode/1f972.png?v8", - "smiling_face_with_three_hearts": "unicode/1f970.png?v8", - "smiling_imp": "unicode/1f608.png?v8", - "smirk": "unicode/1f60f.png?v8", - "smirk_cat": "unicode/1f63c.png?v8", - "smoking": "unicode/1f6ac.png?v8", - "snail": "unicode/1f40c.png?v8", - "snake": "unicode/1f40d.png?v8", - "sneezing_face": "unicode/1f927.png?v8", - "snowboarder": "unicode/1f3c2.png?v8", - "snowflake": "unicode/2744.png?v8", - "snowman": "unicode/26c4.png?v8", - "snowman_with_snow": "unicode/2603.png?v8", - "soap": "unicode/1f9fc.png?v8", - "sob": "unicode/1f62d.png?v8", - "soccer": "unicode/26bd.png?v8", - "socks": "unicode/1f9e6.png?v8", - "softball": "unicode/1f94e.png?v8", - "solomon_islands": "unicode/1f1f8-1f1e7.png?v8", - "somalia": "unicode/1f1f8-1f1f4.png?v8", - "soon": "unicode/1f51c.png?v8", - "sos": "unicode/1f198.png?v8", - "sound": "unicode/1f509.png?v8", - "south_africa": "unicode/1f1ff-1f1e6.png?v8", - "south_georgia_south_sandwich_islands": "unicode/1f1ec-1f1f8.png?v8", - "south_sudan": "unicode/1f1f8-1f1f8.png?v8", - "space_invader": "unicode/1f47e.png?v8", - "spades": "unicode/2660.png?v8", - "spaghetti": "unicode/1f35d.png?v8", - "sparkle": "unicode/2747.png?v8", - "sparkler": "unicode/1f387.png?v8", - "sparkles": "unicode/2728.png?v8", - "sparkling_heart": "unicode/1f496.png?v8", - "speak_no_evil": "unicode/1f64a.png?v8", - "speaker": "unicode/1f508.png?v8", - "speaking_head": "unicode/1f5e3.png?v8", - "speech_balloon": "unicode/1f4ac.png?v8", - "speedboat": "unicode/1f6a4.png?v8", - "spider": "unicode/1f577.png?v8", - "spider_web": "unicode/1f578.png?v8", - "spiral_calendar": "unicode/1f5d3.png?v8", - "spiral_notepad": "unicode/1f5d2.png?v8", - "sponge": "unicode/1f9fd.png?v8", - "spoon": "unicode/1f944.png?v8", - "squid": "unicode/1f991.png?v8", - "sri_lanka": "unicode/1f1f1-1f1f0.png?v8", - "st_barthelemy": "unicode/1f1e7-1f1f1.png?v8", - "st_helena": "unicode/1f1f8-1f1ed.png?v8", - "st_kitts_nevis": "unicode/1f1f0-1f1f3.png?v8", - "st_lucia": "unicode/1f1f1-1f1e8.png?v8", - "st_martin": "unicode/1f1f2-1f1eb.png?v8", - "st_pierre_miquelon": "unicode/1f1f5-1f1f2.png?v8", - "st_vincent_grenadines": "unicode/1f1fb-1f1e8.png?v8", - "stadium": "unicode/1f3df.png?v8", - "standing_man": "unicode/1f9cd-2642.png?v8", - "standing_person": "unicode/1f9cd.png?v8", - "standing_woman": "unicode/1f9cd-2640.png?v8", - "star": "unicode/2b50.png?v8", - "star2": "unicode/1f31f.png?v8", - "star_and_crescent": "unicode/262a.png?v8", - "star_of_david": "unicode/2721.png?v8", - "star_struck": "unicode/1f929.png?v8", - "stars": "unicode/1f320.png?v8", - "station": "unicode/1f689.png?v8", - "statue_of_liberty": "unicode/1f5fd.png?v8", - "steam_locomotive": "unicode/1f682.png?v8", - "stethoscope": "unicode/1fa7a.png?v8", - "stew": "unicode/1f372.png?v8", - "stop_button": "unicode/23f9.png?v8", - "stop_sign": "unicode/1f6d1.png?v8", - "stopwatch": "unicode/23f1.png?v8", - "straight_ruler": "unicode/1f4cf.png?v8", - "strawberry": "unicode/1f353.png?v8", - "stuck_out_tongue": "unicode/1f61b.png?v8", - "stuck_out_tongue_closed_eyes": "unicode/1f61d.png?v8", - "stuck_out_tongue_winking_eye": "unicode/1f61c.png?v8", - "student": "unicode/1f9d1-1f393.png?v8", - "studio_microphone": "unicode/1f399.png?v8", - "stuffed_flatbread": "unicode/1f959.png?v8", - "sudan": "unicode/1f1f8-1f1e9.png?v8", - "sun_behind_large_cloud": "unicode/1f325.png?v8", - "sun_behind_rain_cloud": "unicode/1f326.png?v8", - "sun_behind_small_cloud": "unicode/1f324.png?v8", - "sun_with_face": "unicode/1f31e.png?v8", - "sunflower": "unicode/1f33b.png?v8", - "sunglasses": "unicode/1f60e.png?v8", - "sunny": "unicode/2600.png?v8", - "sunrise": "unicode/1f305.png?v8", - "sunrise_over_mountains": "unicode/1f304.png?v8", - "superhero": "unicode/1f9b8.png?v8", - "superhero_man": "unicode/1f9b8-2642.png?v8", - "superhero_woman": "unicode/1f9b8-2640.png?v8", - "supervillain": "unicode/1f9b9.png?v8", - "supervillain_man": "unicode/1f9b9-2642.png?v8", - "supervillain_woman": "unicode/1f9b9-2640.png?v8", - "surfer": "unicode/1f3c4.png?v8", - "surfing_man": "unicode/1f3c4-2642.png?v8", - "surfing_woman": "unicode/1f3c4-2640.png?v8", - "suriname": "unicode/1f1f8-1f1f7.png?v8", - "sushi": "unicode/1f363.png?v8", - "suspect": "suspect.png?v8", - "suspension_railway": "unicode/1f69f.png?v8", - "svalbard_jan_mayen": "unicode/1f1f8-1f1ef.png?v8", - "swan": "unicode/1f9a2.png?v8", - "swaziland": "unicode/1f1f8-1f1ff.png?v8", - "sweat": "unicode/1f613.png?v8", - "sweat_drops": "unicode/1f4a6.png?v8", - "sweat_smile": "unicode/1f605.png?v8", - "sweden": "unicode/1f1f8-1f1ea.png?v8", - "sweet_potato": "unicode/1f360.png?v8", - "swim_brief": "unicode/1fa72.png?v8", - "swimmer": "unicode/1f3ca.png?v8", - "swimming_man": "unicode/1f3ca-2642.png?v8", - "swimming_woman": "unicode/1f3ca-2640.png?v8", - "switzerland": "unicode/1f1e8-1f1ed.png?v8", - "symbols": "unicode/1f523.png?v8", - "synagogue": "unicode/1f54d.png?v8", - "syria": "unicode/1f1f8-1f1fe.png?v8", - "syringe": "unicode/1f489.png?v8", - "t-rex": "unicode/1f996.png?v8", - "taco": "unicode/1f32e.png?v8", - "tada": "unicode/1f389.png?v8", - "taiwan": "unicode/1f1f9-1f1fc.png?v8", - "tajikistan": "unicode/1f1f9-1f1ef.png?v8", - "takeout_box": "unicode/1f961.png?v8", - "tamale": "unicode/1fad4.png?v8", - "tanabata_tree": "unicode/1f38b.png?v8", - "tangerine": "unicode/1f34a.png?v8", - "tanzania": "unicode/1f1f9-1f1ff.png?v8", - "taurus": "unicode/2649.png?v8", - "taxi": "unicode/1f695.png?v8", - "tea": "unicode/1f375.png?v8", - "teacher": "unicode/1f9d1-1f3eb.png?v8", - "teapot": "unicode/1fad6.png?v8", - "technologist": "unicode/1f9d1-1f4bb.png?v8", - "teddy_bear": "unicode/1f9f8.png?v8", - "telephone": "unicode/260e.png?v8", - "telephone_receiver": "unicode/1f4de.png?v8", - "telescope": "unicode/1f52d.png?v8", - "tennis": "unicode/1f3be.png?v8", - "tent": "unicode/26fa.png?v8", - "test_tube": "unicode/1f9ea.png?v8", - "thailand": "unicode/1f1f9-1f1ed.png?v8", - "thermometer": "unicode/1f321.png?v8", - "thinking": "unicode/1f914.png?v8", - "thong_sandal": "unicode/1fa74.png?v8", - "thought_balloon": "unicode/1f4ad.png?v8", - "thread": "unicode/1f9f5.png?v8", - "three": "unicode/0033-20e3.png?v8", - "thumbsdown": "unicode/1f44e.png?v8", - "thumbsup": "unicode/1f44d.png?v8", - "ticket": "unicode/1f3ab.png?v8", - "tickets": "unicode/1f39f.png?v8", - "tiger": "unicode/1f42f.png?v8", - "tiger2": "unicode/1f405.png?v8", - "timer_clock": "unicode/23f2.png?v8", - "timor_leste": "unicode/1f1f9-1f1f1.png?v8", - "tipping_hand_man": "unicode/1f481-2642.png?v8", - "tipping_hand_person": "unicode/1f481.png?v8", - "tipping_hand_woman": "unicode/1f481-2640.png?v8", - "tired_face": "unicode/1f62b.png?v8", - "tm": "unicode/2122.png?v8", - "togo": "unicode/1f1f9-1f1ec.png?v8", - "toilet": "unicode/1f6bd.png?v8", - "tokelau": "unicode/1f1f9-1f1f0.png?v8", - "tokyo_tower": "unicode/1f5fc.png?v8", - "tomato": "unicode/1f345.png?v8", - "tonga": "unicode/1f1f9-1f1f4.png?v8", - "tongue": "unicode/1f445.png?v8", - "toolbox": "unicode/1f9f0.png?v8", - "tooth": "unicode/1f9b7.png?v8", - "toothbrush": "unicode/1faa5.png?v8", - "top": "unicode/1f51d.png?v8", - "tophat": "unicode/1f3a9.png?v8", - "tornado": "unicode/1f32a.png?v8", - "tr": "unicode/1f1f9-1f1f7.png?v8", - "trackball": "unicode/1f5b2.png?v8", - "tractor": "unicode/1f69c.png?v8", - "traffic_light": "unicode/1f6a5.png?v8", - "train": "unicode/1f68b.png?v8", - "train2": "unicode/1f686.png?v8", - "tram": "unicode/1f68a.png?v8", - "transgender_flag": "unicode/1f3f3-26a7.png?v8", - "transgender_symbol": "unicode/26a7.png?v8", - "triangular_flag_on_post": "unicode/1f6a9.png?v8", - "triangular_ruler": "unicode/1f4d0.png?v8", - "trident": "unicode/1f531.png?v8", - "trinidad_tobago": "unicode/1f1f9-1f1f9.png?v8", - "tristan_da_cunha": "unicode/1f1f9-1f1e6.png?v8", - "triumph": "unicode/1f624.png?v8", - "trolleybus": "unicode/1f68e.png?v8", - "trollface": "trollface.png?v8", - "trophy": "unicode/1f3c6.png?v8", - "tropical_drink": "unicode/1f379.png?v8", - "tropical_fish": "unicode/1f420.png?v8", - "truck": "unicode/1f69a.png?v8", - "trumpet": "unicode/1f3ba.png?v8", - "tshirt": "unicode/1f455.png?v8", - "tulip": "unicode/1f337.png?v8", - "tumbler_glass": "unicode/1f943.png?v8", - "tunisia": "unicode/1f1f9-1f1f3.png?v8", - "turkey": "unicode/1f983.png?v8", - "turkmenistan": "unicode/1f1f9-1f1f2.png?v8", - "turks_caicos_islands": "unicode/1f1f9-1f1e8.png?v8", - "turtle": "unicode/1f422.png?v8", - "tuvalu": "unicode/1f1f9-1f1fb.png?v8", - "tv": "unicode/1f4fa.png?v8", - "twisted_rightwards_arrows": "unicode/1f500.png?v8", - "two": "unicode/0032-20e3.png?v8", - "two_hearts": "unicode/1f495.png?v8", - "two_men_holding_hands": "unicode/1f46c.png?v8", - "two_women_holding_hands": "unicode/1f46d.png?v8", - "u5272": "unicode/1f239.png?v8", - "u5408": "unicode/1f234.png?v8", - "u55b6": "unicode/1f23a.png?v8", - "u6307": "unicode/1f22f.png?v8", - "u6708": "unicode/1f237.png?v8", - "u6709": "unicode/1f236.png?v8", - "u6e80": "unicode/1f235.png?v8", - "u7121": "unicode/1f21a.png?v8", - "u7533": "unicode/1f238.png?v8", - "u7981": "unicode/1f232.png?v8", - "u7a7a": "unicode/1f233.png?v8", - "uganda": "unicode/1f1fa-1f1ec.png?v8", - "uk": "unicode/1f1ec-1f1e7.png?v8", - "ukraine": "unicode/1f1fa-1f1e6.png?v8", - "umbrella": "unicode/2614.png?v8", - "unamused": "unicode/1f612.png?v8", - "underage": "unicode/1f51e.png?v8", - "unicorn": "unicode/1f984.png?v8", - "united_arab_emirates": "unicode/1f1e6-1f1ea.png?v8", - "united_nations": "unicode/1f1fa-1f1f3.png?v8", - "unlock": "unicode/1f513.png?v8", - "up": "unicode/1f199.png?v8", - "upside_down_face": "unicode/1f643.png?v8", - "uruguay": "unicode/1f1fa-1f1fe.png?v8", - "us": "unicode/1f1fa-1f1f8.png?v8", - "us_outlying_islands": "unicode/1f1fa-1f1f2.png?v8", - "us_virgin_islands": "unicode/1f1fb-1f1ee.png?v8", - "uzbekistan": "unicode/1f1fa-1f1ff.png?v8", - "v": "unicode/270c.png?v8", - "vampire": "unicode/1f9db.png?v8", - "vampire_man": "unicode/1f9db-2642.png?v8", - "vampire_woman": "unicode/1f9db-2640.png?v8", - "vanuatu": "unicode/1f1fb-1f1fa.png?v8", - "vatican_city": "unicode/1f1fb-1f1e6.png?v8", - "venezuela": "unicode/1f1fb-1f1ea.png?v8", - "vertical_traffic_light": "unicode/1f6a6.png?v8", - "vhs": "unicode/1f4fc.png?v8", - "vibration_mode": "unicode/1f4f3.png?v8", - "video_camera": "unicode/1f4f9.png?v8", - "video_game": "unicode/1f3ae.png?v8", - "vietnam": "unicode/1f1fb-1f1f3.png?v8", - "violin": "unicode/1f3bb.png?v8", - "virgo": "unicode/264d.png?v8", - "volcano": "unicode/1f30b.png?v8", - "volleyball": "unicode/1f3d0.png?v8", - "vomiting_face": "unicode/1f92e.png?v8", - "vs": "unicode/1f19a.png?v8", - "vulcan_salute": "unicode/1f596.png?v8", - "waffle": "unicode/1f9c7.png?v8", - "wales": "unicode/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f.png?v8", - "walking": "unicode/1f6b6.png?v8", - "walking_man": "unicode/1f6b6-2642.png?v8", - "walking_woman": "unicode/1f6b6-2640.png?v8", - "wallis_futuna": "unicode/1f1fc-1f1eb.png?v8", - "waning_crescent_moon": "unicode/1f318.png?v8", - "waning_gibbous_moon": "unicode/1f316.png?v8", - "warning": "unicode/26a0.png?v8", - "wastebasket": "unicode/1f5d1.png?v8", - "watch": "unicode/231a.png?v8", - "water_buffalo": "unicode/1f403.png?v8", - "water_polo": "unicode/1f93d.png?v8", - "watermelon": "unicode/1f349.png?v8", - "wave": "unicode/1f44b.png?v8", - "wavy_dash": "unicode/3030.png?v8", - "waxing_crescent_moon": "unicode/1f312.png?v8", - "waxing_gibbous_moon": "unicode/1f314.png?v8", - "wc": "unicode/1f6be.png?v8", - "weary": "unicode/1f629.png?v8", - "wedding": "unicode/1f492.png?v8", - "weight_lifting": "unicode/1f3cb.png?v8", - "weight_lifting_man": "unicode/1f3cb-2642.png?v8", - "weight_lifting_woman": "unicode/1f3cb-2640.png?v8", - "western_sahara": "unicode/1f1ea-1f1ed.png?v8", - "whale": "unicode/1f433.png?v8", - "whale2": "unicode/1f40b.png?v8", - "wheel_of_dharma": "unicode/2638.png?v8", - "wheelchair": "unicode/267f.png?v8", - "white_check_mark": "unicode/2705.png?v8", - "white_circle": "unicode/26aa.png?v8", - "white_flag": "unicode/1f3f3.png?v8", - "white_flower": "unicode/1f4ae.png?v8", - "white_haired_man": "unicode/1f468-1f9b3.png?v8", - "white_haired_woman": "unicode/1f469-1f9b3.png?v8", - "white_heart": "unicode/1f90d.png?v8", - "white_large_square": "unicode/2b1c.png?v8", - "white_medium_small_square": "unicode/25fd.png?v8", - "white_medium_square": "unicode/25fb.png?v8", - "white_small_square": "unicode/25ab.png?v8", - "white_square_button": "unicode/1f533.png?v8", - "wilted_flower": "unicode/1f940.png?v8", - "wind_chime": "unicode/1f390.png?v8", - "wind_face": "unicode/1f32c.png?v8", - "window": "unicode/1fa9f.png?v8", - "wine_glass": "unicode/1f377.png?v8", - "wink": "unicode/1f609.png?v8", - "wolf": "unicode/1f43a.png?v8", - "woman": "unicode/1f469.png?v8", - "woman_artist": "unicode/1f469-1f3a8.png?v8", - "woman_astronaut": "unicode/1f469-1f680.png?v8", - "woman_beard": "unicode/1f9d4-2640.png?v8", - "woman_cartwheeling": "unicode/1f938-2640.png?v8", - "woman_cook": "unicode/1f469-1f373.png?v8", - "woman_dancing": "unicode/1f483.png?v8", - "woman_facepalming": "unicode/1f926-2640.png?v8", - "woman_factory_worker": "unicode/1f469-1f3ed.png?v8", - "woman_farmer": "unicode/1f469-1f33e.png?v8", - "woman_feeding_baby": "unicode/1f469-1f37c.png?v8", - "woman_firefighter": "unicode/1f469-1f692.png?v8", - "woman_health_worker": "unicode/1f469-2695.png?v8", - "woman_in_manual_wheelchair": "unicode/1f469-1f9bd.png?v8", - "woman_in_motorized_wheelchair": "unicode/1f469-1f9bc.png?v8", - "woman_in_tuxedo": "unicode/1f935-2640.png?v8", - "woman_judge": "unicode/1f469-2696.png?v8", - "woman_juggling": "unicode/1f939-2640.png?v8", - "woman_mechanic": "unicode/1f469-1f527.png?v8", - "woman_office_worker": "unicode/1f469-1f4bc.png?v8", - "woman_pilot": "unicode/1f469-2708.png?v8", - "woman_playing_handball": "unicode/1f93e-2640.png?v8", - "woman_playing_water_polo": "unicode/1f93d-2640.png?v8", - "woman_scientist": "unicode/1f469-1f52c.png?v8", - "woman_shrugging": "unicode/1f937-2640.png?v8", - "woman_singer": "unicode/1f469-1f3a4.png?v8", - "woman_student": "unicode/1f469-1f393.png?v8", - "woman_teacher": "unicode/1f469-1f3eb.png?v8", - "woman_technologist": "unicode/1f469-1f4bb.png?v8", - "woman_with_headscarf": "unicode/1f9d5.png?v8", - "woman_with_probing_cane": "unicode/1f469-1f9af.png?v8", - "woman_with_turban": "unicode/1f473-2640.png?v8", - "woman_with_veil": "unicode/1f470-2640.png?v8", - "womans_clothes": "unicode/1f45a.png?v8", - "womans_hat": "unicode/1f452.png?v8", - "women_wrestling": "unicode/1f93c-2640.png?v8", - "womens": "unicode/1f6ba.png?v8", - "wood": "unicode/1fab5.png?v8", - "woozy_face": "unicode/1f974.png?v8", - "world_map": "unicode/1f5fa.png?v8", - "worm": "unicode/1fab1.png?v8", - "worried": "unicode/1f61f.png?v8", - "wrench": "unicode/1f527.png?v8", - "wrestling": "unicode/1f93c.png?v8", - "writing_hand": "unicode/270d.png?v8", - "x": "unicode/274c.png?v8", - "yarn": "unicode/1f9f6.png?v8", - "yawning_face": "unicode/1f971.png?v8", - "yellow_circle": "unicode/1f7e1.png?v8", - "yellow_heart": "unicode/1f49b.png?v8", - "yellow_square": "unicode/1f7e8.png?v8", - "yemen": "unicode/1f1fe-1f1ea.png?v8", - "yen": "unicode/1f4b4.png?v8", - "yin_yang": "unicode/262f.png?v8", - "yo_yo": "unicode/1fa80.png?v8", - "yum": "unicode/1f60b.png?v8", - "zambia": "unicode/1f1ff-1f1f2.png?v8", - "zany_face": "unicode/1f92a.png?v8", - "zap": "unicode/26a1.png?v8", - "zebra": "unicode/1f993.png?v8", - "zero": "unicode/0030-20e3.png?v8", - "zimbabwe": "unicode/1f1ff-1f1fc.png?v8", - "zipper_mouth_face": "unicode/1f910.png?v8", - "zombie": "unicode/1f9df.png?v8", - "zombie_man": "unicode/1f9df-2642.png?v8", - "zombie_woman": "unicode/1f9df-2640.png?v8", - "zzz": "unicode/1f4a4.png?v8" - } - }; - - function replaceEmojiShorthand(m, $1, useNativeEmoji) { - var emojiMatch = emojiData.data[$1]; - - var result = m; - - if (emojiMatch) { - if (useNativeEmoji && /unicode/.test(emojiMatch)) { - var emojiUnicode = emojiMatch - .replace('unicode/', '') - .replace(/\.png.*/, '') - .split('-') - .map(function (u) { return ("&#x" + u + ";"); }) - // Separate multi-character emoji with zero width joiner sequence (ZWJ) - // Hat tip: https://about.gitlab.com/blog/2018/05/30/journey-in-native-unicode-emoji/#emoji-made-up-of-multiple-characters - .join('‍') - .concat('︎'); - result = "" + emojiUnicode + ""; - } else { - result = "\"""; - } - } - - return result; - } - - function emojify(text, useNativeEmoji) { - return ( - text - // Mark colons in tags - .replace( - /<(code|pre|script|template)[^>]*?>[\s\S]+?<\/(code|pre|script|template)>/g, - function (m) { return m.replace(/:/g, '__colon__'); } - ) - // Mark colons in comments - .replace(//g, function (m) { return m.replace(/:/g, '__colon__'); }) - // Replace emoji shorthand codes - .replace(/:([a-z0-9_\-+]+?):/g, function (m, $1) { return replaceEmojiShorthand(m, $1, useNativeEmoji); } - ) - // Restore colons in tags and comments - .replace(/__colon__/g, ':') - ); - } - - /** - * Converts a colon formatted string to a object with properties. - * - * This is process a provided string and look for any tokens in the format - * of `:name[=value]` and then convert it to a object and return. - * An example of this is ':include :type=code :fragment=demo' is taken and - * then converted to: - * - * ``` - * { - * include: '', - * type: 'code', - * fragment: 'demo' - * } - * ``` - * - * @param {string} str The string to parse. - * - * @return {object} The original string and parsed object, { str, config }. - */ - function getAndRemoveConfig(str) { - if ( str === void 0 ) str = ''; - - var config = {}; - - if (str) { - str = str - .replace(/^('|")/, '') - .replace(/('|")$/, '') - .replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g, function (m, key, value) { - if (key.indexOf(':') === -1) { - config[key] = (value && value.replace(/"/g, '')) || true; - return ''; - } - - return m; - }) - .trim(); - } - - return { str: str, config: config }; - } - - /** - * Remove the tag from sidebar when the header with link, details see issue 1069 - * @param {string} str The string to deal with. - * - * @return {string} str The string after delete the element. - */ - function removeAtag(str) { - if ( str === void 0 ) str = ''; - - return str.replace(/(<\/?a.*?>)/gi, ''); - } - - var imageCompiler = function (ref) { - var renderer = ref.renderer; - var contentBase = ref.contentBase; - var router = ref.router; - - return (renderer.image = function (href, title, text) { - var url = href; - var attrs = []; - - var ref = getAndRemoveConfig(title); - var str = ref.str; - var config = ref.config; - title = str; - - if (config['no-zoom']) { - attrs.push('data-no-zoom'); - } - - if (title) { - attrs.push(("title=\"" + title + "\"")); - } - - if (config.size) { - var ref$1 = config.size.split('x'); - var width = ref$1[0]; - var height = ref$1[1]; - if (height) { - attrs.push(("width=\"" + width + "\" height=\"" + height + "\"")); - } else { - attrs.push(("width=\"" + width + "\"")); - } - } - - if (config.class) { - attrs.push(("class=\"" + (config.class) + "\"")); - } - - if (config.id) { - attrs.push(("id=\"" + (config.id) + "\"")); - } - - if (!isAbsolutePath(href)) { - url = getPath(contentBase, getParentPath(router.getCurrentPath()), href); - } - - if (attrs.length > 0) { - return ("\"""); - } - - return ("\"""); - }); - }; - - var prism = createCommonjsModule(function (module) { - /* ********************************************** - Begin prism-core.js - ********************************************** */ - - /// - - var _self = (typeof window !== 'undefined') - ? window // if in browser - : ( - (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) - ? self // if in worker - : {} // if in node js - ); - - /** - * Prism: Lightweight, robust, elegant syntax highlighting - * - * @license MIT - * @author Lea Verou - * @namespace - * @public - */ - var Prism = (function (_self) { - - // Private helper vars - var lang = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i; - var uniqueId = 0; - - // The grammar object for plaintext - var plainTextGrammar = {}; - - - var _ = { - /** - * By default, Prism will attempt to highlight all code elements (by calling {@link Prism.highlightAll}) on the - * current page after the page finished loading. This might be a problem if e.g. you wanted to asynchronously load - * additional languages or plugins yourself. - * - * By setting this value to `true`, Prism will not automatically highlight all code elements on the page. - * - * You obviously have to change this value before the automatic highlighting started. To do this, you can add an - * empty Prism object into the global scope before loading the Prism script like this: - * - * ```js - * window.Prism = window.Prism || {}; - * Prism.manual = true; - * // add a new