├── .gitignore
├── .idea
├── compiler.xml
├── encodings.xml
├── jarRepositories.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── ORGIN_README.md
├── README.md
├── Wechat8Spellbook
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── maven.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── kotlin
│ │ └── com
│ │ │ └── gh0u1l5
│ │ │ └── wechatmagician
│ │ │ └── spellbook
│ │ │ └── MirrorUnitTest.kt
│ └── resources
│ │ └── apks
│ │ ├── rename.sh
│ │ └── simplify.sh
│ ├── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── com
│ │ └── gh0u1l5
│ │ └── wechatmagician
│ │ └── spellbook
│ │ ├── Alias.kt
│ │ ├── C.kt
│ │ ├── SpellBook.kt
│ │ ├── WechatGlobal.kt
│ │ ├── WechatStatus.kt
│ │ ├── base
│ │ ├── Classes.kt
│ │ ├── EventCenter.kt
│ │ ├── Hooker.kt
│ │ ├── HookerProvider.kt
│ │ ├── Operation.kt
│ │ ├── Version.kt
│ │ └── WaitChannel.kt
│ │ ├── hookers
│ │ ├── Activities.kt
│ │ ├── Adapters.kt
│ │ ├── Database.kt
│ │ ├── FileSystem.kt
│ │ ├── ListViewHider.kt
│ │ ├── MenuAppender.kt
│ │ ├── Notifications.kt
│ │ ├── SearchBar.kt
│ │ ├── Storage.kt
│ │ ├── UriRouter.kt
│ │ ├── XLog.kt
│ │ └── XmlParser.kt
│ │ ├── interfaces
│ │ ├── IActivityHook.kt
│ │ ├── IAdapterHook.kt
│ │ ├── IDatabaseHook.kt
│ │ ├── IFileSystemHook.kt
│ │ ├── IImageStorageHook.kt
│ │ ├── IMessageStorageHook.kt
│ │ ├── INotificationHook.kt
│ │ ├── IPopupMenuHook.kt
│ │ ├── ISearchBarConsole.kt
│ │ ├── IUriRouterHook.kt
│ │ ├── IXLogHook.kt
│ │ └── IXmlParserHook.kt
│ │ ├── mirror
│ │ ├── MirrorClasses.kt
│ │ ├── MirrorFields.kt
│ │ ├── MirrorMethods.kt
│ │ ├── android
│ │ │ └── support
│ │ │ │ └── v4
│ │ │ │ └── app
│ │ │ │ └── Classes.kt
│ │ └── com
│ │ │ └── tencent
│ │ │ ├── mars
│ │ │ └── xlog
│ │ │ │ ├── Classes.kt
│ │ │ │ ├── Constants.kt
│ │ │ │ └── Methods.kt
│ │ │ ├── mm
│ │ │ ├── Classes.kt
│ │ │ ├── Fields.kt
│ │ │ ├── Methods.kt
│ │ │ ├── booter
│ │ │ │ └── notification
│ │ │ │ │ ├── Classes.kt
│ │ │ │ │ └── queue
│ │ │ │ │ ├── Classes.kt
│ │ │ │ │ └── Methods.kt
│ │ │ ├── modelsfs
│ │ │ │ ├── Classes.kt
│ │ │ │ └── Methods.kt
│ │ │ ├── plugin
│ │ │ │ ├── base
│ │ │ │ │ └── stub
│ │ │ │ │ │ ├── Classes.kt
│ │ │ │ │ │ └── Methods.kt
│ │ │ │ ├── gallery
│ │ │ │ │ └── ui
│ │ │ │ │ │ └── Classes.kt
│ │ │ │ ├── remittance
│ │ │ │ │ └── ui
│ │ │ │ │ │ └── Classes.kt
│ │ │ │ ├── sns
│ │ │ │ │ └── ui
│ │ │ │ │ │ ├── Classes.kt
│ │ │ │ │ │ └── Fields.kt
│ │ │ │ └── webwx
│ │ │ │ │ └── ui
│ │ │ │ │ └── Classes.kt
│ │ │ ├── sdk
│ │ │ │ └── platformtools
│ │ │ │ │ ├── Classes.kt
│ │ │ │ │ └── Methods.kt
│ │ │ ├── storage
│ │ │ │ ├── Classes.kt
│ │ │ │ └── Methods.kt
│ │ │ └── ui
│ │ │ │ ├── Classes.kt
│ │ │ │ ├── Methods.kt
│ │ │ │ ├── base
│ │ │ │ └── Classes.kt
│ │ │ │ ├── chatting
│ │ │ │ └── Classes.kt
│ │ │ │ ├── contact
│ │ │ │ └── Classes.kt
│ │ │ │ ├── conversation
│ │ │ │ └── Classes.kt
│ │ │ │ ├── tools
│ │ │ │ └── Classes.kt
│ │ │ │ └── transmit
│ │ │ │ ├── Classes.kt
│ │ │ │ └── Methods.kt
│ │ │ └── wcdb
│ │ │ ├── Classes.kt
│ │ │ ├── Package.kt
│ │ │ ├── database
│ │ │ └── Classes.kt
│ │ │ └── support
│ │ │ └── Classes.kt
│ │ ├── parser
│ │ ├── ApkFile.kt
│ │ ├── ClassTrie.kt
│ │ ├── DexHeader.kt
│ │ └── DexParser.kt
│ │ └── util
│ │ ├── BasicUtil.kt
│ │ ├── FileUtil.kt
│ │ ├── MirrorUtil.kt
│ │ ├── ParallelUtil.kt
│ │ ├── ReflectionUtil.kt
│ │ └── XposedUtil.kt
│ └── test
│ └── kotlin
│ └── com
│ └── gh0u1l5
│ └── wechatmagician
│ └── spellbook
│ ├── base
│ └── VersionUnitTest.kt
│ └── mirror
│ └── ReflectionUnitTest.kt
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── src
│ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── huruwo
│ │ │ └── app_xposed
│ │ │ └── ExampleInstrumentedTest.java
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── assets
│ │ │ └── xposed_init
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── huruwo
│ │ │ │ └── hposed
│ │ │ │ ├── HookLoader.java
│ │ │ │ ├── MainHookLoader.java
│ │ │ │ ├── action
│ │ │ │ ├── Alert.java
│ │ │ │ └── Message.java
│ │ │ │ ├── net
│ │ │ │ ├── ApiClient.java
│ │ │ │ ├── AppApiService.java
│ │ │ │ ├── AppDataRepository.java
│ │ │ │ ├── ScalarRequestBodyConverter.java
│ │ │ │ ├── ScalarResponseBodyConverters.java
│ │ │ │ ├── ScalarsConverterFactory.java
│ │ │ │ └── SubscriberManager.java
│ │ │ │ ├── ui
│ │ │ │ └── MainActivity.java
│ │ │ │ └── utils
│ │ │ │ ├── Constants.java
│ │ │ │ ├── GsonUtils.java
│ │ │ │ ├── LogXUtils.java
│ │ │ │ └── XSharePrefeUtil.java
│ │ └── res
│ │ │ ├── layout
│ │ │ └── activity_main.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── test
│ │ └── java
│ │ └── com
│ │ └── huruwo
│ │ └── app_xposed
│ │ └── ExampleUnitTest.java
└── xx.jks
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release
└── output.json
├── settings.gradle
└── xxx.jks
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # IntelliJ
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/gradle.xml
42 | .idea/assetWizardSettings.xml
43 | .idea/dictionaries
44 | .idea/libraries
45 | .idea/caches
46 |
47 | # Keystore files
48 | # Uncomment the following line if you do not want to check your keystore files in.
49 | #*.jks
50 |
51 | # External native build folder generated in Android Studio 2.2 and later
52 | .externalNativeBuild
53 |
54 | # Google Services (e.g. APIs or Firebase)
55 | google-services.json
56 |
57 | # Freeline
58 | freeline.py
59 | freeline/
60 | freeline_project_description.json
61 |
62 | # fastlane
63 | fastlane/report.xml
64 | fastlane/Preview.html
65 | fastlane/screenshots
66 | fastlane/test_output
67 | fastlane/readme.md
68 |
69 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ORGIN_README.md:
--------------------------------------------------------------------------------
1 | # Wechat Spellbook
2 |
3 | [](https://travis-ci.org/Gh0u1L5/WechatSpellbook) [](https://maven-badges.herokuapp.com/maven-central/com.github.gh0u1l5/wechat-spellbook)
4 |
5 | ---
6 |
7 | ### 2018.11.24 状态更新
8 |
9 | 最近半年实在是太忙了,一直没有更新项目。
10 |
11 | 最近先是花了一天多时间,解决了一下拖了很久的性能问题。通过重写APK Parser、启用新的数据结构和并行计算模块,把性能提升了10倍不止,详情可以参见这两个commit:[5bf7804](../../commit/5bf7804664606dd6280d5a0dc6e33f3a9ffbb5a6) & [388e25f](../../commit/388e25f904e73633f6639ae1b9e1aa1d959607cb)
12 |
13 | 然后,就是把项目中的注释几乎全盘改成了中文,方便以后的交流。剩下还没有改完的部分诚求热心人帮一下忙,翻译起来实在是太累了。
14 |
15 | 接下来会找机会把目前提交的issue看一下,把6.6.x及以上的微信版本都适配一下,不过这可能要等到12月底了吧。
16 |
17 | ---
18 |
19 | Wechat Spellbook 是一个使用Kotlin编写的开源微信插件框架,底层需要 [Xposed](https://forum.xda-developers.com/xposed) 或 [VirtualXposed](https://github.com/android-hacker/VirtualXposed) 等Hooking框架的支持,而顶层可以轻松对接Java、Kotlin、Scala等JVM系语言。让程序员能够在几分钟内编写出简单的微信插件,随意揉捏微信的内部逻辑。
20 |
21 | 另外,在编写项目文档的过程中,也会找机会向大家分享一些逆向微信的经验和适配不同操作系统踩到的坑,也欢迎大家把自己的经验分享上来自由讨论。
22 |
23 | ## 便利特色
24 |
25 | * __精心设计各项机制,合理运用多线程和惰性求值等技巧,用不到的功能永远不会影响你的性能。__
26 | * 使用一套API __自动分析__ 微信内部结构特征,__避免手工适配__ 每个微信版本不同的类名、方法名。
27 | - 每次微信更新的时候,都会使用写好的单元测试自动验证是否有特征失效。
28 | - 精心设计的框架保证了开发者可以轻松拓展添加自己需要的特征。
29 | * 框架内部设计了 _EventCenter_ 和 _HookerProvider_ 两类不同的事件处理方式。
30 | - _EventCenter_ 让开发者直接使用设计好的事件消息来截获微信数据,保证 __便利性__ 。
31 | - _HookerProvider_ 允许熟悉Xposed的开发者调用Xposed接口进行自由发挥,保证 __自由度__ 。
32 | * 正确使用 _EventCenter_ 方案,有助于回避Xposed的一些小问题,如
33 | - 函数调用被前一个劫持者打断导致的插件相互冲突。
34 | - Xposed自Android 7.0后偶发的,由于多线程导致ART崩溃的问题。
35 |
36 | ## 衍生项目
37 | * [WechatMagician](https://github.com/Gh0u1L5/WechatMagician)
38 | * [WechatBotXposed](https://github.com/Blankeer/WechatBotXposed)
39 |
40 | ## 开发文档
41 | * [简介](https://github.com/Gh0u1L5/WechatSpellbook/wiki/Home)
42 | * [快速上手](https://github.com/Gh0u1L5/WechatSpellbook/wiki/快速上手)
43 | * 开发教程
44 | - [事件机制](https://github.com/Gh0u1L5/WechatSpellbook/wiki/事件机制)
45 | - 反射集合
46 | - 混淆与自动适配
47 | * 逆向技巧
48 | - 常见逆向工具
49 | - 调试输出
50 | - 堆栈跟踪
51 | - Support库
52 | - 免重启调试插件
53 |
54 |
55 | ## 关于VirtualXposed
56 |
57 | 目前对于VirtualXposed的支持还算不上完善,因为VirtualXposed的环境和原生Xposed的环境实在是差了太多,我和weishu折腾了很久才算是在部分设备上解决了黑屏卡死的问题。现在我们俩发布代码的日子里,一个拜三清一个拜关公,希望能够帮Bug们早日往生。
58 |
59 | ## 写给开发者
60 |
61 | 在出于兴趣接触微信逆向的短短一年里,我接触到了形形色色的开发者、投资者、支持者。被微信本身复杂成熟的架构深深吸引的同时,也惊异于微信衍生出来的灰色产业之庞大、第三方微信竞争之激烈。
62 | 有不少朋友劝我闭源、商业化,而且拿出了很多细致的想法,我很感激他们的帮助,但是最后还是决定在开源的方向上再次迈出了一大步,原因有二。
63 |
64 | 其一,我不想停在这里。商业化必然牵扯大量的时间精力和权益纠葛。我不想在自己正处在上升期的时候,就草率地把大量的时间精力都花在一个刚满20岁的时候意外做出来的小成就上,然后吃上十几年的老本。而且这个项目,归根结底是一个寄生在微信上的项目。这种格局,跟我每天在实习岗位上接触到的、跟大学里的同学谈论到的,都根本不在一个层面上。我虽然知道自己的才华有限,但是只要有机会的话,我还是想看看更高处的风景。
65 |
66 | 其二,我太过理想主义。中国互联网在很早的时候就已经是一个相当商业化的世界了。我并不反对商业化,也不觉得商业化有任何的道德问题。人,总归是要吃饭的。像fkzhang这样能靠自己的才华吃饭,既不偷也不抢,这有什么错呢?但是我心里总归是感觉到失落的,因为我最早爱上的,是一个自由的互联网,是一个为每个有才华有求知欲的年轻人准备的儿童乐园。只要思想用0和1表达出来,就再也没有什么规则能够阻挡他们,他们就是儿童乐园之王。我希望当我国的年轻一代对计算机产生好奇时,也能像西方国家的孩子们一样,能够轻松地、无语言障碍地,接触到大量好玩有趣的个人开源项目,进而爱上这个0与1的世界。如果有年轻的初中、高中的学弟学妹,能够用我的项目把微信像橡皮泥一样随意揉捏,像当年的我一样深深地享受到这个世界的乐趣,那么对我实在是一种莫大的鼓励与快乐。
67 |
68 | 当然,对于诸位想要基于我的项目做商业项目的开发者,也请尽管拿去。我不喜欢道德绑架别人,也不认为这有什么绑架的必要。但是如果你在闲暇时间能够贡献十几行代码、修复些你发现的Bug,那就已经十分感激。
69 |
70 | ## 打赏二维码
71 |
72 | 应 [Issue #5](https://github.com/Gh0u1L5/WechatSpellbook/issues/5) 的请求,贴上打赏二维码,让我们回归平和的技术讨论。
73 |
74 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## 新增基于WeChat8Xposed的微信机器人示例
3 |
4 | 提供基本的功能:
5 |
6 | 1.微信群消息自动接收
7 |
8 | 2.微信群消息自动回复
9 |
10 | 3.关键词触发以及后台管理
11 |
12 |
13 | -------------------------
14 |
15 | # 项目说明
16 |
17 | 本项目拷贝自 [https://github.com/Gh0u1L5/WechatSpellbook](https://github.com/Gh0u1L5/WechatSpellbook)
18 |
19 | 个人尝试做新版微信的适配计划
20 |
21 | 因为原来的项目因为已经停更了很久 在新版的微信上会有些问题
22 | 本人针对新版的微信 尤其是8版本 做了全新的适配 使其在新版的微信上也可以使用
23 |
24 |
25 | # 基于Wechat8Spellbook的聊天机器人项目
26 |
27 | [WeChatSimpleBot](https://github.com/HuRuWo/WeChatSimpleBot)
28 |
29 | - 目前代码还没有上传 因为包含了私人信息,需要处理
30 |
31 | ## 为了方便大家理解 本人做了一些相关的笔记 如何依赖 Wechat8Spellbook 做插件开发
32 |
33 | [1.WeChat8Xposed通用hook框架适配新版微信-单元测试适配](https://www.huruwo.top/wechat8xposed%e9%80%9a%e7%94%a8hook%e6%a1%86%e6%9e%b6%e9%80%82%e9%85%8d%e6%96%b0%e7%89%88%e5%be%ae%e4%bf%a1-%e5%8d%95%e5%85%83%e6%b5%8b%e8%af%95%e9%80%82%e9%85%8d%e6%96%b0%e5%be%ae%e4%bf%a1/)
34 |
35 | [2.WeChat8Xposed通用hook框架适配新版微信-修复NotificationManagerCompat](https://www.huruwo.top/wechat8xposed%e9%80%9a%e7%94%a8hook%e6%a1%86%e6%9e%b6%e9%80%82%e9%85%8d%e6%96%b0%e7%89%88%e5%be%ae%e4%bf%a1-%e4%bf%ae%e5%a4%8dnotificationmanagercompat%e9%80%82%e9%85%8d%e5%bc%82%e5%b8%b8/)
36 |
37 |
38 | ## 更新说明
39 |
40 | |更新时间| 更新内容|更新说明|
41 | |----|----| ----|
42 | | 更新计划 | 更加详细的开发文档编写 ||
43 | |更新计划|com.tencent.mm modelsfs 异常修复||
44 | | 2021-8-9 | com.tencent.mm notification异常修复 |notification和消息通知相关的兼容类|
45 | |2021-6-23 | 验证修复wcdb包下的类是否路径异常做修复调整||
46 | |2021-6-20 | 修正部分类的特征值 包括有 NotificationManagerCompat 等类的视频 即v4兼容包切换到 androidx 兼容包||
47 | |2021-6-19 |测试代码测试最新版微信的适配程度||
48 | |2021-6-18 |项目结构调整||
49 |
50 |
51 | ## 使用说明
52 |
53 | Wechat Spellbook 是一个使用Kotlin编写的开源微信插件框架
54 |
55 | 详情可以原来项目的README说明[ORGIN_README.md](ORGIN_README.md)
56 |
57 | ## 其他说明
58 |
59 | 个人认为这套框架非常有意思,但是作者停更了非常可惜。
60 | 两个有意思的地方在于:
61 |
62 | 1.提供一套合理的框架给第三方开发者 简化开发步骤
63 |
64 | 2.通过apk的dex分析做出一套忽略版本的差异的 并且可以添加新的特征规则
65 |
66 | ## 鸣谢
67 |
68 | [Gh0u1L5](https://github.com/Gh0u1L5)
69 | 提供的项目
70 | [https://github.com/Gh0u1L5/WechatSpellbook](https://github.com/Gh0u1L5/WechatSpellbook) 支持
71 |
72 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/workspace.xml
38 | .idea/tasks.xml
39 | .idea/gradle.xml
40 | .idea/dictionaries
41 | .idea/libraries
42 |
43 | # Keystore files
44 | *.jks
45 |
46 | # External native build folder generated in Android Studio 2.2 and later
47 | .externalNativeBuild
48 |
49 | # Google Services (e.g. APIs or Firebase)
50 | google-services.json
51 |
52 | # Freeline
53 | freeline.py
54 | freeline/
55 | freeline_project_description.json
56 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | jdk: oraclejdk8
3 |
4 | env:
5 | global:
6 | - GRADLE_VERSION=4.10
7 | - BUILD_API=android-28
8 | - BUILD_TOOLS=build-tools-28.0.3
9 |
10 | android:
11 | components:
12 | - tools
13 | - platform-tools
14 | # The SDK version used to compile your project
15 | - $BUILD_API
16 | # The BuildTools version used by your project
17 | - $BUILD_TOOLS
18 |
19 | before_cache:
20 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
21 | - rm -rf $HOME/.gradle/caches/*/plugin-resolution/
22 |
23 | cache:
24 | directories:
25 | - $HOME/.gradle/caches/
26 | - $HOME/.gradle/wrapper/
27 | - $HOME/.android/build-cache
28 |
29 | before_install:
30 | # Setup the correct gradle version.
31 | - wget http://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip
32 | - unzip gradle-${GRADLE_VERSION}-bin.zip
33 | - export GRADLE_HOME=$PWD/gradle-${GRADLE_VERSION}
34 | - export PATH=$GRADLE_HOME/bin:$PATH
35 |
36 | script:
37 | - gradle build check
38 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Emerson Lin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/README.md:
--------------------------------------------------------------------------------
1 | # Wechat Spellbook
2 |
3 | [](https://travis-ci.org/Gh0u1L5/WechatSpellbook) [](https://maven-badges.herokuapp.com/maven-central/com.github.gh0u1l5/wechat-spellbook)
4 |
5 | ---
6 |
7 | ### 2018.11.24 状态更新
8 |
9 | 最近半年实在是太忙了,一直没有更新项目。
10 |
11 | 最近先是花了一天多时间,解决了一下拖了很久的性能问题。通过重写APK Parser、启用新的数据结构和并行计算模块,把性能提升了10倍不止,详情可以参见这两个commit:[5bf7804](../../commit/5bf7804664606dd6280d5a0dc6e33f3a9ffbb5a6) & [388e25f](../../commit/388e25f904e73633f6639ae1b9e1aa1d959607cb)
12 |
13 | 然后,就是把项目中的注释几乎全盘改成了中文,方便以后的交流。剩下还没有改完的部分诚求热心人帮一下忙,翻译起来实在是太累了。
14 |
15 | 接下来会找机会把目前提交的issue看一下,把6.6.x及以上的微信版本都适配一下,不过这可能要等到12月底了吧。
16 |
17 | ---
18 |
19 | Wechat Spellbook 是一个使用Kotlin编写的开源微信插件框架,底层需要 [Xposed](https://forum.xda-developers.com/xposed) 或 [VirtualXposed](https://github.com/android-hacker/VirtualXposed) 等Hooking框架的支持,而顶层可以轻松对接Java、Kotlin、Scala等JVM系语言。让程序员能够在几分钟内编写出简单的微信插件,随意揉捏微信的内部逻辑。
20 |
21 | 另外,在编写项目文档的过程中,也会找机会向大家分享一些逆向微信的经验和适配不同操作系统踩到的坑,也欢迎大家把自己的经验分享上来自由讨论。
22 |
23 | ## 便利特色
24 |
25 | * __精心设计各项机制,合理运用多线程和惰性求值等技巧,用不到的功能永远不会影响你的性能。__
26 | * 使用一套API __自动分析__ 微信内部结构特征,__避免手工适配__ 每个微信版本不同的类名、方法名。
27 | - 每次微信更新的时候,都会使用写好的单元测试自动验证是否有特征失效。
28 | - 精心设计的框架保证了开发者可以轻松拓展添加自己需要的特征。
29 | * 框架内部设计了 _EventCenter_ 和 _HookerProvider_ 两类不同的事件处理方式。
30 | - _EventCenter_ 让开发者直接使用设计好的事件消息来截获微信数据,保证 __便利性__ 。
31 | - _HookerProvider_ 允许熟悉Xposed的开发者调用Xposed接口进行自由发挥,保证 __自由度__ 。
32 | * 正确使用 _EventCenter_ 方案,有助于回避Xposed的一些小问题,如
33 | - 函数调用被前一个劫持者打断导致的插件相互冲突。
34 | - Xposed自Android 7.0后偶发的,由于多线程导致ART崩溃的问题。
35 |
36 | ## 衍生项目
37 | * [WechatMagician](https://github.com/Gh0u1L5/WechatMagician)
38 | * [WechatBotXposed](https://github.com/Blankeer/WechatBotXposed)
39 |
40 | ## 开发文档
41 | * [简介](https://github.com/Gh0u1L5/WechatSpellbook/wiki/Home)
42 | * [快速上手](https://github.com/Gh0u1L5/WechatSpellbook/wiki/快速上手)
43 | * 开发教程
44 | - [事件机制](https://github.com/Gh0u1L5/WechatSpellbook/wiki/事件机制)
45 | - 反射集合
46 | - 混淆与自动适配
47 | * 逆向技巧
48 | - 常见逆向工具
49 | - 调试输出
50 | - 堆栈跟踪
51 | - Support库
52 | - 免重启调试插件
53 |
54 |
55 | ## 关于VirtualXposed
56 |
57 | 目前对于VirtualXposed的支持还算不上完善,因为VirtualXposed的环境和原生Xposed的环境实在是差了太多,我和weishu折腾了很久才算是在部分设备上解决了黑屏卡死的问题。现在我们俩发布代码的日子里,一个拜三清一个拜关公,希望能够帮Bug们早日往生。
58 |
59 | ## 写给开发者
60 |
61 | 在出于兴趣接触微信逆向的短短一年里,我接触到了形形色色的开发者、投资者、支持者。被微信本身复杂成熟的架构深深吸引的同时,也惊异于微信衍生出来的灰色产业之庞大、第三方微信竞争之激烈。
62 | 有不少朋友劝我闭源、商业化,而且拿出了很多细致的想法,我很感激他们的帮助,但是最后还是决定在开源的方向上再次迈出了一大步,原因有二。
63 |
64 | 其一,我不想停在这里。商业化必然牵扯大量的时间精力和权益纠葛。我不想在自己正处在上升期的时候,就草率地把大量的时间精力都花在一个刚满20岁的时候意外做出来的小成就上,然后吃上十几年的老本。而且这个项目,归根结底是一个寄生在微信上的项目。这种格局,跟我每天在实习岗位上接触到的、跟大学里的同学谈论到的,都根本不在一个层面上。我虽然知道自己的才华有限,但是只要有机会的话,我还是想看看更高处的风景。
65 |
66 | 其二,我太过理想主义。中国互联网在很早的时候就已经是一个相当商业化的世界了。我并不反对商业化,也不觉得商业化有任何的道德问题。人,总归是要吃饭的。像fkzhang这样能靠自己的才华吃饭,既不偷也不抢,这有什么错呢?但是我心里总归是感觉到失落的,因为我最早爱上的,是一个自由的互联网,是一个为每个有才华有求知欲的年轻人准备的儿童乐园。只要思想用0和1表达出来,就再也没有什么规则能够阻挡他们,他们就是儿童乐园之王。我希望当我国的年轻一代对计算机产生好奇时,也能像西方国家的孩子们一样,能够轻松地、无语言障碍地,接触到大量好玩有趣的个人开源项目,进而爱上这个0与1的世界。如果有年轻的初中、高中的学弟学妹,能够用我的项目把微信像橡皮泥一样随意揉捏,像当年的我一样深深地享受到这个世界的乐趣,那么对我实在是一种莫大的鼓励与快乐。
67 |
68 | 当然,对于诸位想要基于我的项目做商业项目的开发者,也请尽管拿去。我不喜欢道德绑架别人,也不认为这有什么绑架的必要。但是如果你在闲暇时间能够贡献十几行代码、修复些你发现的Bug,那就已经十分感激。
69 |
70 | ## 打赏二维码
71 |
72 | 应 [Issue #5](https://github.com/Gh0u1L5/WechatSpellbook/issues/5) 的请求,贴上打赏二维码,让我们回归平和的技术讨论。
73 |
74 |
75 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/build.gradle:
--------------------------------------------------------------------------------
1 | // 重复添加同样的依赖,以保证Spellbook能够独立编译
2 | buildscript {
3 | ext {
4 | dokka_version = '0.9.17'
5 | kotlin_version = '1.3.10'
6 | }
7 | repositories {
8 | jcenter()
9 | google()
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.2.1'
13 | classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:$dokka_version"
14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
15 | }
16 | }
17 |
18 | apply plugin: 'com.android.library'
19 | apply plugin: 'kotlin-android'
20 | apply plugin: 'org.jetbrains.dokka-android'
21 |
22 | android {
23 | compileSdkVersion 28
24 | defaultConfig {
25 | minSdkVersion 19
26 | targetSdkVersion 28
27 | versionCode 1
28 | versionName "1.0"
29 |
30 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
31 | }
32 | buildTypes {
33 | release {
34 | postprocessing {
35 | removeUnusedCode false
36 | removeUnusedResources false
37 | obfuscate false
38 | optimizeCode false
39 | proguardFile 'proguard-rules.pro'
40 | }
41 | }
42 | }
43 | lintOptions {
44 | abortOnError false
45 | }
46 | sourceSets {
47 | main.java.srcDirs += 'src/main/kotlin'
48 | test.java.srcDirs += 'src/test/kotlin'
49 | androidTest.java.srcDirs += "src/androidTest/kotlin"
50 | }
51 | android.libraryVariants.all { variant ->
52 | variant.javaCompiler.dependsOn(generateMirrorClassesList)
53 | variant.javaCompiler.dependsOn(generateMirrorMethodsList)
54 | variant.javaCompiler.dependsOn(generateMirrorFieldsList)
55 | }
56 | }
57 |
58 | repositories {
59 | jcenter()
60 | google()
61 | }
62 |
63 | dependencies {
64 | testImplementation 'junit:junit:4.12'
65 | androidTestImplementation 'androidx.test:runner:1.1.0'
66 | androidTestImplementation 'androidx.test.ext:junit:1.0.0'
67 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
68 |
69 | //noinspection GradleDependency
70 | compileOnly 'de.robv.android.xposed:api:53'
71 | compileOnly 'de.robv.android.xposed:api:53:sources'
72 | api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
73 | }
74 |
75 | task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaAndroidTask) {
76 | outputFormat = 'javadoc'
77 | outputDirectory = "$buildDir/dokkaJavadoc"
78 | externalDocumentationLink {
79 | url = new URL("https://kotlinlang.org/api/latest/jvm/stdlib/")
80 | }
81 | externalDocumentationLink {
82 | url = new URL("https://developer.android.com/reference/")
83 | }
84 | externalDocumentationLink {
85 | url = new URL("http://api.xposed.info/reference/")
86 | }
87 | }
88 |
89 | def generateMirrorList(String type, String filename) {
90 | def SRC_DIR = "src/main/kotlin/"
91 | def MIRROR_DIR = "src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror"
92 |
93 | def prefixLength = file(SRC_DIR).absolutePath.length() + 1
94 | def classes = fileTree(MIRROR_DIR).filter { file ->
95 | file.name == "${type}.kt"
96 | }.collect { file ->
97 | def relativePath = file.absolutePath.drop(prefixLength)
98 | relativePath.replaceAll('[\\\\/]', '.').substring(0, relativePath.length() - 3)
99 | }.sort()
100 | file("$MIRROR_DIR/${filename}.kt").text = """package com.gh0u1l5.wechatmagician.spellbook.mirror
101 |
102 | /**
103 | * Dynamically generated by build.gradle
104 | */
105 | val $filename = listOf (
106 | ${classes.join(",\n ")}
107 | )
108 | """
109 | }
110 |
111 | task generateMirrorClassesList() {
112 | generateMirrorList("Classes", "MirrorClasses")
113 | }
114 |
115 | task generateMirrorMethodsList() {
116 | generateMirrorList("Methods", "MirrorMethods")
117 | }
118 |
119 | task generateMirrorFieldsList() {
120 | generateMirrorList("Fields", "MirrorFields")
121 | }
122 |
123 | apply from: 'maven.gradle'
--------------------------------------------------------------------------------
/Wechat8Spellbook/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.daemon=true
2 |
3 | POM_NAME=WechatSpellbook
4 | POM_ARTIFACT_ID=wechat-spellbook
5 | POM_PACKAGING=aar
6 |
7 | VERSION_NAME=0.0.6
8 | VERSION_CODE=6
9 | GROUP=com.github.gh0u1l5
10 |
11 | POM_DESCRIPTION=Wechat Spellbook is a powerful and scalable framework, which allows everyone to develop a Wechat Xposed module in minutes.
12 | POM_URL=https://github.com/Gh0u1L5/WechatSpellbook
13 | POM_SCM_URL=https://github.com/Gh0u1L5/WechatSpellbook
14 | POM_SCM_CONNECTION=scm:git@github.com:Gh0u1L5/WechatSpellbook.git
15 | POM_SCM_DEV_CONNECTION=scm:git@github.com:Gh0u1L5/WechatSpellbook.git
16 | POM_LICENCE_NAME=GNU General Public License v3.0
17 | POM_LICENCE_URL=https://www.gnu.org/licenses/gpl-3.0.en.html
18 | POM_LICENCE_DIST=repo
19 | POM_DEVELOPER_ID=Gh0u1L5
20 | POM_DEVELOPER_NAME=Emerson Lin
21 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/maven.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven'
2 | apply plugin: 'signing'
3 |
4 | def isReleaseBuild() {
5 | return !VERSION_NAME.contains("SNAPSHOT")
6 | }
7 |
8 | def getReleaseRepositoryUrl() {
9 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
10 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
11 | }
12 |
13 | def getSnapshotRepositoryUrl() {
14 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
15 | : "https://oss.sonatype.org/content/repositories/snapshots/"
16 | }
17 |
18 | def getRepositoryUsername() {
19 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
20 | }
21 |
22 | def getRepositoryPassword() {
23 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
24 | }
25 |
26 | afterEvaluate { project ->
27 | uploadArchives {
28 | repositories {
29 | mavenDeployer {
30 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
31 |
32 | pom.groupId = GROUP
33 | pom.artifactId = POM_ARTIFACT_ID
34 | pom.version = VERSION_NAME
35 |
36 | repository(url: getReleaseRepositoryUrl()) {
37 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
38 | }
39 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
40 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
41 | }
42 |
43 | pom.project {
44 | name POM_NAME
45 | packaging POM_PACKAGING
46 | description POM_DESCRIPTION
47 | url POM_URL
48 |
49 | scm {
50 | url POM_SCM_URL
51 | connection POM_SCM_CONNECTION
52 | developerConnection POM_SCM_DEV_CONNECTION
53 | }
54 |
55 | licenses {
56 | license {
57 | name POM_LICENCE_NAME
58 | url POM_LICENCE_URL
59 | distribution POM_LICENCE_DIST
60 | }
61 | }
62 |
63 | developers {
64 | developer {
65 | id POM_DEVELOPER_ID
66 | name POM_DEVELOPER_NAME
67 | }
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 | signing {
75 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
76 | sign configurations.archives
77 | }
78 |
79 | task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
80 | classifier = 'javadoc'
81 | from "$buildDir/dokkaJavadoc"
82 | }
83 |
84 | task sourcesJar(type: Jar) {
85 | classifier = 'sources'
86 | from fileTree(dir: 'src/main/kotlin')
87 | }
88 |
89 | artifacts {
90 | archives sourcesJar
91 | archives javadocJar
92 | }
93 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/androidTest/kotlin/com/gh0u1l5/wechatmagician/spellbook/MirrorUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import androidx.test.platform.app.InstrumentationRegistry
7 | import com.gh0u1l5.wechatmagician.spellbook.base.Version
8 | import com.gh0u1l5.wechatmagician.spellbook.mirror.MirrorClasses
9 | import com.gh0u1l5.wechatmagician.spellbook.mirror.MirrorFields
10 | import com.gh0u1l5.wechatmagician.spellbook.mirror.MirrorMethods
11 | import com.gh0u1l5.wechatmagician.spellbook.parser.ApkFile
12 | import com.gh0u1l5.wechatmagician.spellbook.util.FileUtil
13 | import com.gh0u1l5.wechatmagician.spellbook.util.MirrorUtil
14 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil
15 | import dalvik.system.PathClassLoader
16 | import org.junit.Assert.*
17 | import org.junit.Before
18 | import org.junit.Test
19 | import org.junit.runner.RunWith
20 | import java.io.File
21 | import java.lang.ClassLoader.getSystemClassLoader
22 | import kotlin.system.measureTimeMillis
23 |
24 | /**
25 | * 自动化的微信版本适配测试
26 | */
27 | @ExperimentalUnsignedTypes
28 | @RunWith(AndroidJUnit4::class)
29 | class MirrorUnitTest {
30 | companion object {
31 | private const val DOMESTIC_DIR = "apks/domestic"
32 | private const val PLAY_STORE_DIR = "apks/play-store"
33 | }
34 |
35 | private var context: Context? = null
36 |
37 | @Before
38 | fun initialize() {
39 | context = InstrumentationRegistry.getInstrumentation().targetContext
40 | }
41 |
42 | private fun verifyPackage(apkPath: String) {
43 | // 解析 APK 版本
44 | val regex = Regex("wechat-v(.*)\\.apk")
45 | val match = regex.find(apkPath) ?: throw Exception("Unexpected path format")
46 | val version = match.groupValues[1]
47 |
48 | // 将 APK 文件保存至 Cache 目录
49 | val cacheDir = context!!.cacheDir
50 | val apkFile = File(cacheDir, apkPath)
51 | try {
52 | javaClass.classLoader!!.getResourceAsStream(apkPath).use {
53 | FileUtil.writeInputStreamToDisk(apkFile.absolutePath, it)
54 | }
55 | } catch (t: Throwable) {
56 | Log.w("MirrorUnitTest", t)
57 | return // ignore if the apk isn't accessible
58 | }
59 |
60 | // 确保 APK 文件存在 并开始自动化适配测试
61 | assertTrue(apkFile.exists())
62 | ApkFile(apkFile).use {
63 | // 测试 APK Parser 的解析速度
64 | val timeParseDex = measureTimeMillis { it.classTypes }
65 | Log.d("MirrorUnitTest", "Benchmark: Parsing APK takes $timeParseDex ms.")
66 |
67 | // 初始化 WechatGlobal
68 | WechatGlobal.wxUnitTestMode = true
69 | WechatGlobal.wxVersion = Version(version)
70 | WechatGlobal.wxPackageName = "com.tencent.mm"
71 | WechatGlobal.wxLoader = PathClassLoader(apkFile.absolutePath, getSystemClassLoader())
72 | WechatGlobal.wxClasses = it.classTypes
73 |
74 | // 清理上次测试留下的缓存
75 | val objects = MirrorClasses + MirrorMethods + MirrorFields
76 | ReflectionUtil.clearClassCache()
77 | ReflectionUtil.clearMethodCache()
78 | objects.forEach { instance ->
79 | MirrorUtil.clearUnitTestLazyFields(instance)
80 | }
81 |
82 | // 进行适配测试并生成结果
83 | var result: List>? = null
84 | val timeSearch = measureTimeMillis {
85 | result = MirrorUtil.generateReportWithForceEval(objects)
86 | }
87 | Log.d("MirrorUnitTest", "Benchmark: Searching over classes takes $timeSearch ms.")
88 | result?.forEach { entry ->
89 | Log.d("MirrorUnitTest", "Verified: ${entry.first} -> ${entry.second}")
90 | }
91 | }
92 |
93 | apkFile.delete()
94 | }
95 |
96 | @Test
97 | fun verifyDomesticPackage8_0_6() {
98 | verifyPackage("$DOMESTIC_DIR/wechat-v8.0.6.apk")
99 | }
100 |
101 | // @Test fun verifyDomesticPackage6_6_0() {
102 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.0.apk")
103 | // }
104 | //
105 | // @Test fun verifyDomesticPackage6_6_1() {
106 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.1.apk")
107 | // }
108 | //
109 | // @Test fun verifyDomesticPackage6_6_2() {
110 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.2.apk")
111 | // }
112 | //
113 | // @Test fun verifyDomesticPackage6_6_3() {
114 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.3.apk")
115 | // }
116 | //
117 | // @Test fun verifyDomesticPackage6_6_5() {
118 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.5.apk")
119 | // }
120 | //
121 | // @Test fun verifyDomesticPackage6_6_6() {
122 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.6.apk")
123 | // }
124 | //
125 | // @Test fun verifyDomesticPackage6_6_7() {
126 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.6.7.apk")
127 | // }
128 | //
129 | // @Test fun verifyDomesticPackage6_7_2() {
130 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.7.2.apk")
131 | // }
132 | //
133 | // @Test fun verifyDomesticPackage6_7_3() {
134 | // verifyPackage("$DOMESTIC_DIR/wechat-v6.7.3.apk")
135 | // }
136 | //
137 | // @Test fun verifyPlayStorePackage6_6_1() {
138 | // verifyPackage("$PLAY_STORE_DIR/wechat-v6.6.1.apk")
139 | // }
140 | //
141 | // @Test fun verifyPlayStorePackage6_6_2() {
142 | // verifyPackage("$PLAY_STORE_DIR/wechat-v6.6.2.apk")
143 | // }
144 | //
145 | // @Test fun verifyPlayStorePackage6_6_6() {
146 | // verifyPackage("$PLAY_STORE_DIR/wechat-v6.6.6.apk")
147 | // }
148 | //
149 | // @Test fun verifyPlayStorePackage6_6_7() {
150 | // verifyPackage("$PLAY_STORE_DIR/wechat-v6.6.7.apk")
151 | // }
152 | //
153 | // @Test fun verifyPlayStorePackage6_7_3() {
154 | // verifyPackage("$PLAY_STORE_DIR/wechat-v6.7.3.apk")
155 | // }
156 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/androidTest/resources/apks/rename.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | re='(.*?)/com\.tencent\.mm_(.*?)_.*?\.apk'
3 | for file in */*.apk; do
4 | if [[ $file =~ $re ]]; then
5 | mv "$file" "${BASH_REMATCH[1]}/wechat-v${BASH_REMATCH[2]}.apk"
6 | fi
7 | done
8 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/androidTest/resources/apks/simplify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | workdir=`pwd`
4 | re='(.*?)/(.*?)\.apk'
5 |
6 | for file in */*.apk; do
7 | if [[ $file =~ $re ]]; then
8 | basename="${BASH_REMATCH[2]}"
9 | tempdir="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
10 |
11 | unzip "$file" -d "$tempdir"
12 | cd "$tempdir"
13 | rm -rf META-INF assets lib r resources.arsc
14 | zip -9 "${basename}.apk" * && mv "${basename}.apk" ../
15 | cd "$workdir"
16 | rm -rf "$tempdir"
17 | fi
18 | done
19 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/Alias.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook
2 |
3 | typealias Predicate = (Any?) -> Boolean
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/C.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("unused")
2 |
3 | package com.gh0u1l5.wechatmagician.spellbook
4 |
5 | /**
6 | * C.(class name) 是 (class name)::class.java 的缩写形式
7 | *
8 | * 没有什么特别的意义, 仅仅是为了让 Kotlin 代码看起来比较优雅(事逼)
9 | */
10 | object C {
11 | val Boolean = Boolean::class.java
12 | val File = java.io.File::class.java
13 | val FileInputStream = java.io.FileInputStream::class.java
14 | val FileOutputStream = java.io.FileOutputStream::class.java
15 | val Int = Int::class.java
16 | val Iterator = java.util.Iterator::class.java
17 | val Long = Long::class.java
18 | val Map = Map::class.java
19 | val Object = Object::class.java
20 | val String = String::class.java
21 | val Throwable = Throwable::class.java
22 |
23 | val Activity = android.app.Activity::class.java
24 | val AdapterView = android.widget.AdapterView::class.java
25 | val AdapterView_OnItemClickListener = android.widget.AdapterView.OnItemClickListener::class.java
26 | val AttributeSet = android.util.AttributeSet::class.java
27 | val BaseAdapter = android.widget.BaseAdapter::class.java
28 | val Bundle = android.os.Bundle::class.java
29 | val Button = android.widget.Button::class.java
30 | val Configuration = android.content.res.Configuration::class.java
31 | val ContentValues = android.content.ContentValues::class.java
32 | val Context = android.content.Context::class.java
33 | val ContextMenu = android.view.ContextMenu::class.java
34 | val ContextMenuInfo = android.view.ContextMenu.ContextMenuInfo::class.java
35 | val HeaderViewListAdapter = android.widget.HeaderViewListAdapter::class.java
36 | val Intent = android.content.Intent::class.java
37 | val KeyEvent = android.view.KeyEvent::class.java
38 | val ListAdapter = android.widget.ListAdapter::class.java
39 | val ListView = android.widget.ListView::class.java
40 | val Menu = android.view.Menu::class.java
41 | val Message = android.os.Message::class.java
42 | val MotionEvent = android.view.MotionEvent::class.java
43 | val Notification = android.app.Notification::class.java
44 | val NotificationManager = android.app.NotificationManager::class.java
45 | val View = android.view.View::class.java
46 | val ViewGroup = android.view.ViewGroup::class.java
47 |
48 | val ByteArray = ByteArray::class.java
49 | val IntArray = IntArray::class.java
50 | val ObjectArray = Array::class.java
51 | val StringArray = Array::class.java
52 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/SpellBook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook
2 |
3 | import android.content.Context
4 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
5 | import com.gh0u1l5.wechatmagician.spellbook.base.HookerProvider
6 | import com.gh0u1l5.wechatmagician.spellbook.base.Version
7 | import com.gh0u1l5.wechatmagician.spellbook.hookers.*
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ParallelUtil.parallelForEach
9 | import com.gh0u1l5.wechatmagician.spellbook.util.XposedUtil
10 | import de.robv.android.xposed.XposedBridge.log
11 | import de.robv.android.xposed.XposedHelpers.*
12 | import de.robv.android.xposed.callbacks.XC_LoadPackage
13 | import de.robv.android.xposed.IXposedHookLoadPackage
14 | import java.io.File
15 |
16 | /**
17 | * Wechat Magician SpellBook的核心引擎部分
18 | *
19 | * Refer: https://github.com/Gh0u1L5/WechatSpellbook/wiki
20 | */
21 | object SpellBook {
22 | /**
23 | * 目前支持的 [EventCenter] 列表
24 | *
25 | * Refer: https://github.com/Gh0u1L5/WechatSpellbook/wiki/事件机制
26 | */
27 | private val centers: List = listOf(
28 | Activities,
29 | Adapters,
30 | Database,
31 | FileSystem,
32 | MenuAppender,
33 | Notifications,
34 | SearchBar,
35 | Storage,
36 | UriRouter,
37 | XLog,
38 | XmlParser
39 | )
40 |
41 | /**
42 | * 判断当前进程是否为微信的重要进程, 目前会被判定为重要进程的只有主进程和 :tools 进程
43 | *
44 | * @param lpparam 通过重载 [IXposedHookLoadPackage.handleLoadPackage] 方法拿到的
45 | * [XC_LoadPackage.LoadPackageParam] 对象
46 | */
47 | fun isImportantWechatProcess(lpparam: XC_LoadPackage.LoadPackageParam): Boolean {
48 | // 检查进程名
49 | val processName = lpparam.processName
50 | when {
51 | !processName.contains(':') -> {
52 | // 找到主进程 继续
53 | }
54 | processName.endsWith(":tools") -> {
55 | // 找到 :tools 进程 继续
56 | }
57 | else -> return false
58 | }
59 | // 检查微信依赖的JNI库是否存在, 以此判断当前应用是不是微信
60 | val features = listOf (
61 | "libwechatcommon.so",
62 | "libwechatmm.so",
63 | "libwechatnetwork.so",
64 | "libwechatsight.so",
65 | "libwechatxlog.so"
66 | )
67 | return try {
68 | val libraryDir = File(lpparam.appInfo.nativeLibraryDir)
69 | features.filter { filename ->
70 | File(libraryDir, filename).exists()
71 | }.size >= 3
72 | } catch (t: Throwable) { false }
73 | }
74 |
75 | /**
76 | * 利用 Reflection 获取当前的系统 Context
77 | */
78 | fun getSystemContext(): Context {
79 | val activityThreadClass = findClass("android.app.ActivityThread", null)
80 | val activityThread = callStaticMethod(activityThreadClass, "currentActivityThread")
81 | val context = callMethod(activityThread, "getSystemContext") as Context?
82 | return context ?: throw Error("Failed to get system context.")
83 | }
84 |
85 | /**
86 | * 获取指定应用的 APK 路径
87 | */
88 | fun getApplicationApkPath(packageName: String): String {
89 | val pm = getSystemContext().packageManager
90 | val apkPath = pm.getApplicationInfo(packageName, 0)?.publicSourceDir
91 | return apkPath ?: throw Error("Failed to get the APK path of $packageName")
92 | }
93 |
94 | /**
95 | * 获取指定应用的版本号
96 | */
97 | fun getApplicationVersion(packageName: String): Version {
98 | val pm = getSystemContext().packageManager
99 | val versionName = pm.getPackageInfo(packageName, 0)?.versionName
100 | return Version(versionName
101 | ?: throw Error("Failed to get the version of $packageName"))
102 | }
103 |
104 | /**
105 | * 启动 SpellBook 框架, 注册相关插件
106 | *
107 | * @param lpparam 通过重载 [IXposedHookLoadPackage.handleLoadPackage] 方法拿到的
108 | * [XC_LoadPackage.LoadPackageParam] 对象
109 | * @param plugins 由开发者编写的 SpellBook 插件, 这些插件应当实现 [HookerProvider.provideStaticHookers]
110 | * 方法, 或 interfaces 包中提供的标准接口
111 | *
112 | * Refer: https://github.com/Gh0u1L5/WechatSpellbook/wiki/事件机制
113 | */
114 | fun startup(lpparam: XC_LoadPackage.LoadPackageParam, plugins: List?) {
115 | log("Wechat SpellBook: ${plugins?.size ?: 0} plugins.")
116 | WechatGlobal.init(lpparam)
117 | registerPlugins(plugins)
118 | registerHookers(plugins)
119 | }
120 |
121 | /**
122 | * 检查插件是否实现了标准化的接口, 并将它们注册到对应的 [EventCenter] 中
123 | */
124 | private fun registerPlugins(plugins: List?) {
125 | val observers = plugins?.filter { it !is HookerProvider } ?: listOf()
126 | centers.parallelForEach { center ->
127 | center.interfaces.forEach { `interface` ->
128 | observers.forEach { plugin ->
129 | val assignable = `interface`.isAssignableFrom(plugin::class.java)
130 | if (assignable) {
131 | center.register(`interface`, plugin)
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 | /**
139 | * 检查插件中是否存在自定义的事件, 将它们直接注册到 Xposed 框架上
140 | */
141 | private fun registerHookers(plugins: List?) {
142 | val providers = plugins?.filter { it is HookerProvider } ?: listOf()
143 | (providers + listOf(ListViewHider, MenuAppender)).parallelForEach { provider ->
144 | (provider as HookerProvider).provideStaticHookers()?.forEach { hooker ->
145 | if (!hooker.hasHooked) {
146 | XposedUtil.postHooker(hooker)
147 | }
148 | }
149 | }
150 | }
151 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/WechatGlobal.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook
2 |
3 | import android.widget.Adapter
4 | import android.widget.BaseAdapter
5 | import com.gh0u1l5.wechatmagician.spellbook.SpellBook.getApplicationVersion
6 | import com.gh0u1l5.wechatmagician.spellbook.base.Version
7 | import com.gh0u1l5.wechatmagician.spellbook.base.WaitChannel
8 | import com.gh0u1l5.wechatmagician.spellbook.parser.ApkFile
9 | import com.gh0u1l5.wechatmagician.spellbook.parser.ClassTrie
10 | import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.tryAsynchronously
11 | import de.robv.android.xposed.IXposedHookLoadPackage
12 | import de.robv.android.xposed.callbacks.XC_LoadPackage
13 | import java.lang.ref.WeakReference
14 |
15 | /**
16 | * 用于记录所有关于 Wechat 的关键全局变量
17 | */
18 | object WechatGlobal {
19 |
20 | /**
21 | * 若初始化操作耗费2秒以上, 视作初始化失败, 直接让微信开始正常运行
22 | */
23 | @Suppress("MemberVisibilityCanBePrivate")
24 | const val INIT_TIMEOUT = 2000L // ms
25 |
26 | /**
27 | * 用于防止其他线程在初始化完成之前访问 WechatGlobal的变量
28 | */
29 | private val initChannel = WaitChannel()
30 |
31 | /**
32 | * 微信版本
33 | *
34 | * 如果初始化还未完成的话, 访问该对象的线程会自动阻塞 [INIT_TIMEOUT] ms
35 | */
36 | @Volatile var wxVersion: Version? = null
37 | get() {
38 | if (!wxUnitTestMode) {
39 | initChannel.wait(INIT_TIMEOUT)
40 | initChannel.done()
41 | }
42 | return field
43 | }
44 |
45 | /**
46 | * 微信包名(用于处理多开的情况)
47 | *
48 | * 如果初始化还未完成的话, 访问该对象的线程会自动阻塞 [INIT_TIMEOUT] ms
49 | */
50 | @Volatile var wxPackageName: String = ""
51 | get() {
52 | if (!wxUnitTestMode) {
53 | initChannel.wait(INIT_TIMEOUT)
54 | initChannel.done()
55 | }
56 | return field
57 | }
58 |
59 | /**
60 | * 微信 APK 所使用的 ClassLoader, 用于加载 Class 对象
61 | *
62 | * 如果初始化还未完成的话, 访问该对象的线程会自动阻塞 [INIT_TIMEOUT] ms
63 | */
64 | @Volatile var wxLoader: ClassLoader? = null
65 | get() {
66 | if (!wxUnitTestMode) {
67 | initChannel.wait(INIT_TIMEOUT)
68 | initChannel.done()
69 | }
70 | return field
71 | }
72 |
73 | /**
74 | * 微信 APK 所包含的全部类名, 依据 package 结构组织在一起, 用于动态适配不同的微信版本
75 | *
76 | * 如果初始化还未完成的话, 访问该对象的线程会自动阻塞 [INIT_TIMEOUT] ms
77 | */
78 | @Volatile var wxClasses: ClassTrie? = null
79 | get() {
80 | if (!wxUnitTestMode) {
81 | initChannel.wait(INIT_TIMEOUT)
82 | initChannel.done()
83 | }
84 | return field
85 | }
86 |
87 | /**
88 | * 单元测试模式的开关, 只应该在单元测试中打开
89 | */
90 | @Volatile var wxUnitTestMode: Boolean = false
91 |
92 | // 缓存一些重要的微信全局对象
93 | @Volatile var AddressAdapterObject: WeakReference = WeakReference(null)
94 | @Volatile var ConversationAdapterObject: WeakReference = WeakReference(null)
95 | @Volatile var SnsUserUIAdapterObject: WeakReference = WeakReference(null)
96 | @Volatile var MsgStorageObject: Any? = null
97 | @Volatile var ImgStorageObject: Any? = null
98 | @Volatile var MainDatabaseObject: Any? = null
99 | @Volatile var SnsDatabaseObject: Any? = null
100 |
101 | /**
102 | * 创建一个惰性求值对象, 只有被用到的时候才会自动求值
103 | *
104 | * 当单元测试模式开启的时候, 会使用不同的 Lazy Implementation 辅助测试
105 | *
106 | * @param name 对象名称, 打印错误日志的时候会用到
107 | * @param initializer 用来求值的回调函数
108 | */
109 | inline fun wxLazy(name: String, crossinline initializer: () -> T?): Lazy {
110 | return if (wxUnitTestMode) {
111 | UnitTestLazyImpl {
112 | initializer() ?: throw Error("Failed to evaluate $name")
113 | }
114 | } else {
115 | lazy(LazyThreadSafetyMode.PUBLICATION) {
116 | when (null) {
117 | wxVersion -> throw Error("Invalid wxVersion")
118 | wxPackageName -> throw Error("Invalid wxPackageName")
119 | wxLoader -> throw Error("Invalid wxLoader")
120 | wxClasses -> throw Error("Invalid wxClasses")
121 | }
122 | initializer() ?: throw Error("Failed to evaluate $name")
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * 用来帮助单元测试的一个 Lazy Implementation, 允许开发者多次初始化一个惰性求值对象
129 | */
130 | class UnitTestLazyImpl(private val initializer: () -> T): Lazy, java.io.Serializable {
131 | @Volatile private var lazyValue: Lazy = lazy(initializer)
132 |
133 | fun refresh() {
134 | lazyValue = lazy(initializer)
135 | }
136 |
137 | override val value: T
138 | get() = lazyValue.value
139 |
140 | override fun toString(): String = lazyValue.toString()
141 |
142 | override fun isInitialized(): Boolean = lazyValue.isInitialized()
143 | }
144 |
145 | /**
146 | * 初始化当前的 [WechatGlobal]
147 | *
148 | * @param lpparam 通过重载 [IXposedHookLoadPackage.handleLoadPackage] 方法拿到的
149 | * [XC_LoadPackage.LoadPackageParam] 对象
150 | */
151 | @JvmStatic fun init(lpparam: XC_LoadPackage.LoadPackageParam) {
152 | tryAsynchronously {
153 | if (initChannel.isDone()) {
154 | return@tryAsynchronously
155 | }
156 |
157 | try {
158 | wxVersion = getApplicationVersion(lpparam.packageName)
159 | wxPackageName = lpparam.packageName
160 | wxLoader = lpparam.classLoader
161 |
162 | ApkFile(lpparam.appInfo.sourceDir).use {
163 | wxClasses = it.classTypes
164 | }
165 | } finally {
166 | initChannel.done()
167 | }
168 | }
169 | }
170 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/WechatStatus.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
4 |
5 | /**
6 | * 用来记录各个 [EventCenter] 运行状态的单例对象
7 | */
8 | object WechatStatus {
9 |
10 | /**
11 | * 目前支持的所有功能的标识
12 | */
13 | enum class StatusFlag {
14 | STATUS_FLAG_ACTIVITIES,
15 | STATUS_FLAG_ADAPTERS,
16 | STATUS_FLAG_BASE_ADAPTER,
17 | STATUS_FLAG_COMMAND,
18 | STATUS_FLAG_CONTACT_POPUP,
19 | STATUS_FLAG_CONVERSATION_POPUP,
20 | STATUS_FLAG_DATABASE,
21 | STATUS_FLAG_FILESYSTEM,
22 | STATUS_FLAG_IMG_STORAGE,
23 | STATUS_FLAG_MSG_STORAGE,
24 | STATUS_FLAG_NOTIFICATIONS,
25 | STATUS_FLAG_RESOURCES,
26 | STATUS_FLAG_URI_ROUTER,
27 | STATUS_FLAG_XML_PARSER
28 | }
29 |
30 | /**
31 | * 用于记录所有成功启动的功能
32 | */
33 | private var valid: IntArray = intArrayOf()
34 |
35 | /**
36 | * 报告当前活跃的功能
37 | */
38 | @Synchronized fun report(): IntArray = valid
39 |
40 | /**
41 | * 记录某功能启动完成
42 | */
43 | @Synchronized fun toggle(flag: StatusFlag) { valid += flag.ordinal }
44 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | import android.util.Log
4 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findFieldIfExists
5 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findFieldsWithType
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodExactIfExists
7 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
8 |
9 | /**
10 | * 一组 Class 对象的集合, 可以通过调用不同的 filter 函数筛选得到想要的结果
11 | */
12 | class Classes(private val classes: List>) {
13 | /**
14 | * @suppress
15 | */
16 | private companion object {
17 | private const val TAG = "Reflection"
18 | }
19 |
20 | fun filterBySuper(superClass: Class<*>?): Classes {
21 | return Classes(classes.filter { it.superclass == superClass }.also {
22 | if (it.isEmpty()) {
23 | Log.w(TAG, "filterBySuper found nothing, super class = ${superClass?.simpleName}")
24 | }
25 | })
26 | }
27 |
28 | fun filterByEnclosingClass(enclosingClass: Class<*>?): Classes {
29 | return Classes(classes.filter { it.enclosingClass == enclosingClass }.also {
30 | if (it.isEmpty()) {
31 | Log.w(TAG, "filterByEnclosingClass found nothing, enclosing class = ${enclosingClass?.simpleName} ")
32 | }
33 | })
34 | }
35 |
36 | fun filterByMethod(returnType: Class<*>?, methodName: String, vararg parameterTypes: Class<*>): Classes {
37 | return Classes(classes.filter { clazz ->
38 | val method = findMethodExactIfExists(clazz, methodName, *parameterTypes)
39 | method != null && method.returnType == returnType ?: method.returnType
40 | }.also {
41 | if (it.isEmpty()) {
42 | Log.w(TAG, "filterByMethod found nothing, returnType = ${returnType?.simpleName}, methodName = $methodName, parameterTypes = ${parameterTypes.joinToString("|") { it.simpleName }}")
43 | }
44 | })
45 | }
46 |
47 | fun filterByMethod(returnType: Class<*>?, vararg parameterTypes: Class<*>): Classes {
48 | return Classes(classes.filter { clazz ->
49 | findMethodsByExactParameters(clazz, returnType, *parameterTypes).isNotEmpty()
50 | }.also {
51 | if (it.isEmpty()) {
52 | Log.w(TAG, "filterByMethod found nothing, returnType = ${returnType?.simpleName}, parameterTypes = ${parameterTypes.joinToString("|") { it.simpleName }}")
53 | }
54 | })
55 | }
56 |
57 | fun filterByField(fieldName: String, fieldType: String): Classes {
58 | return Classes(classes.filter { clazz ->
59 | val field = findFieldIfExists(clazz, fieldName)
60 | field != null && field.type.canonicalName == fieldType
61 | }.also {
62 | if (it.isEmpty()) {
63 | Log.w(TAG, "filterByField found nothing, fieldName = $fieldName, fieldType = $fieldType")
64 | }
65 | })
66 | }
67 |
68 | fun filterByField(fieldType: String): Classes {
69 | return Classes(classes.filter { clazz ->
70 | findFieldsWithType(clazz, fieldType).isNotEmpty()
71 | }.also {
72 | if (it.isEmpty()) {
73 | Log.w(TAG, "filterByField found nothing, fieldType = $fieldType")
74 | }
75 | })
76 | }
77 |
78 | fun firstOrNull(): Class<*>? {
79 | if (classes.size > 1) {
80 | val names = classes.map { it.canonicalName }
81 | Log.w("Xposed", "found a signature that matches more than one class: $names")
82 | }
83 | return classes.firstOrNull()
84 | }
85 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/EventCenter.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.tryVerbosely
4 | import com.gh0u1l5.wechatmagician.spellbook.util.ParallelUtil.parallelForEach
5 | import com.gh0u1l5.wechatmagician.spellbook.util.XposedUtil
6 | import de.robv.android.xposed.XC_MethodHook
7 | import java.util.concurrent.ConcurrentHashMap
8 |
9 | /**
10 | * SpellBook 框架的事件中心, 用于提供标准化的事件通知
11 | */
12 | abstract class EventCenter: HookerProvider {
13 |
14 | /**
15 | * 事件中心所支持的接口列表, 任何想要注册到该中心的插件必须实现其中起码一个接口
16 | */
17 | abstract val interfaces: List>
18 |
19 | /**
20 | * 不同事件所对应的观察者列表
21 | */
22 | private val observers: MutableMap> = ConcurrentHashMap()
23 |
24 | /**
25 | * 判断指定插件对象是否关注了某个事件
26 | *
27 | * "关注" 的判断标准是这个对象有没有直接实现一个和事件同名的方法, 从基类继承的方法不算在内
28 | */
29 | private fun Any.hasEvent(event: String) =
30 | this::class.java.declaredMethods.any { it.name == event }
31 |
32 | /**
33 | * 向事件中心注册一个观察者
34 | *
35 | * @param event 观察者关注的事件
36 | * @param observer 观察者本人
37 | */
38 | private fun register(event: String, observer: Any) {
39 | if (observer.hasEvent(event)) {
40 | val hooker = provideEventHooker(event)
41 | if (hooker != null && !hooker.hasHooked) {
42 | XposedUtil.postHooker(hooker)
43 | }
44 | val existing = observers[event] ?: emptySet()
45 | observers[event] = existing + observer
46 | }
47 | }
48 |
49 | /**
50 | * 向事件中心注册一个插件
51 | *
52 | * @param inface 该插件所实现的接口, 必须为 [interfaces] 中存在的接口
53 | * @param plugin 插件对象
54 | */
55 | fun register(inface: Class<*>, plugin: Any) {
56 | inface.methods.forEach { method ->
57 | register(method.name, plugin)
58 | }
59 | }
60 |
61 | /**
62 | * 找到关注某个事件的所有观察者, 若不存在则返回 null
63 | */
64 | fun findObservers(event: String): Set? = observers[event]
65 |
66 | /**
67 | * 通知所有正在观察某个事件的观察者
68 | *
69 | * @param event 具体发生的事件
70 | * @param action 对观察者进行通知的回调函数
71 | */
72 | inline fun notify(event: String, action: (Any) -> Unit) {
73 | findObservers(event)?.forEach {
74 | tryVerbosely { action(it) }
75 | }
76 | }
77 |
78 | /**
79 | * 通知所有正在观察某个事件的观察者(并行计算版本)
80 | *
81 | * @param event 具体发生的事件
82 | * @param action 对观察者进行通知的回调函数
83 | */
84 | inline fun notifyParallel(event: String, crossinline action: (Any) -> Unit) {
85 | findObservers(event)?.parallelForEach { observer ->
86 | tryVerbosely { action(observer) }
87 | }
88 | }
89 |
90 | /**
91 | * 通知所有正在观察某个事件的观察者, 并收集它们的反馈
92 | *
93 | * @param event 具体发生的事件
94 | * @param action 对观察者进行通知的回调函数
95 | */
96 | inline fun notifyForResults(event: String, action: (Any) -> T?): List {
97 | return findObservers(event)?.mapNotNull {
98 | tryVerbosely { action(it) }
99 | } ?: emptyList()
100 | }
101 |
102 | /**
103 | * 通知所有正在观察某个事件的观察者, 并收集它们的反馈, 以确认是否需要拦截该事件
104 | *
105 | * 如果有任何一个观察者返回了 true, 我们就认定当前事件是一个需要被拦截的事件. 例如当微信写文件的时候, 某个观察者
106 | * 检查过文件路径后返回了 true, 那么框架就会拦截这次写文件操作, 向微信返回一个默认值
107 | *
108 | * @param event 具体发生的事件
109 | * @param param 拦截函数调用后得到的 [XC_MethodHook.MethodHookParam] 对象
110 | * @param default 跳过函数调用之后, 仍然需要向 caller 提供一个返回值
111 | * @param action 对观察者进行通知的回调函数
112 | */
113 | inline fun notifyForBypassFlags(event: String, param: XC_MethodHook.MethodHookParam, default: Any? = null, action: (Any) -> Boolean) {
114 | val shouldBypass = notifyForResults(event, action).any()
115 | if (shouldBypass) {
116 | param.result = default
117 | }
118 | }
119 |
120 | /**
121 | * 通知所有正在观察某个事件的观察者, 并收集它们的反馈, 以确认该对这次事件采取什么操作
122 | *
123 | * 在获取了观察者建议的操作之后, 我们会对这些操作的优先级进行排序, 从优先级最高的操作中选择一个予以采纳
124 | *
125 | * @param event 具体发生的事件
126 | * @param param 拦截函数调用后得到的 [XC_MethodHook.MethodHookParam] 对象
127 | * @param action 对观察者进行通知的回调函数
128 | */
129 | inline fun notifyForOperations(event: String, param: XC_MethodHook.MethodHookParam, action: (Any) -> Operation<*>) {
130 | val operations = notifyForResults(event, action)
131 | val result = operations.filter { it.returnEarly }.maxBy { it.priority }
132 | if (result != null) {
133 | if (result.value != null) {
134 | param.result = result.value
135 | }
136 | if (result.error != null) {
137 | param.throwable = result.error
138 | }
139 | }
140 | }
141 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/Hooker.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | /**
4 | * 将一次 Hook 操作封装成对象, 防止对同样的函数反复下钩, 造成难以调查的BUG
5 | *
6 | * 这个类是线程安全的, 多个线程同时调用只会有一个线程成功下钩
7 | *
8 | * @property hooker 实际向 Xposed 框架注册钩子的回调函数
9 | * @constructor 将一次 Hook 操作封装成一个 Hooker 对象
10 | */
11 | class Hooker(private val hooker: () -> Unit) {
12 | /**
13 | * 用来防止重复 Hook 的标记
14 | */
15 | var hasHooked = false
16 | private set
17 |
18 | /**
19 | * 尝试执行一次 Hook 操作, 如果已经钩过了就不再重复
20 | */
21 | @Synchronized fun hook() {
22 | if (!hasHooked) {
23 | hooker()
24 | hasHooked = true
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/HookerProvider.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | /**
4 | * 一个 HookerProvider 可以向 SpellBook 框架提供自定义的钩子, 框架将根据具体的情况将这些钩子注册到 Xposed 框架里
5 | */
6 | interface HookerProvider {
7 | /**
8 | * 返回一组静态钩子, 即传统的 Xposed 钩子
9 | *
10 | * 不论发生啥事, 这些钩子都会被注册到 Xposed 里
11 | */
12 | fun provideStaticHookers(): List? = null
13 |
14 | /**
15 | * 返回一个针对具体事件进行监听的钩子
16 | *
17 | * 只有在某个插件要求监听某个事件的情况下, 对应的钩子才会被注册, 在目前的版本中, 只有 [EventCenter] 才需要实
18 | * 现该方法
19 | *
20 | * WARN: 对于同一个事件, 请返回相同的 Hooker 对象, 这样可以有效避免重复的 hook 行为, 根除潜在的 Bug
21 | */
22 | fun provideEventHooker(event: String): Hooker? = null
23 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/Operation.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | /**
4 | * 当插件监听到某个事件发生, 并拦截到相应的函数调用的时候, 插件可能会需要对拦截住的函数进行某些操作, 这个操作需要被封
5 | * 装成一个 [Operation] 对象传递给 SpellBook 框架
6 | */
7 | class Operation(
8 | val value: T? = null,
9 | val error: Throwable? = null,
10 | val priority: Int = 0,
11 | val returnEarly: Boolean = false
12 | ) {
13 | companion object {
14 | /**
15 | * 创建一个空操作, 表明自己什么也不做
16 | */
17 | @JvmStatic fun nop(priority: Int = 0): Operation {
18 | return Operation(priority = priority)
19 | }
20 |
21 | /**
22 | * 创建一个打断操作, 跳过原函数的执行, 直接抛出一个异常
23 | *
24 | * @param error 要抛出的异常
25 | * @param priority 操作的优先级, 当多个插件同时做出操作的时候, 框架将选取优先级较高的结果, 优先级相同的
26 | * 情况下随机选择一个操作
27 | */
28 | @JvmStatic fun interruption(error: Throwable, priority: Int = 0): Operation {
29 | return Operation(error = error, priority = priority, returnEarly = true)
30 | }
31 |
32 | /**
33 | * 创建一个替换操作, 跳过原函数的执行, 直接返回一个结果
34 | *
35 | * @param value 要返回的结果
36 | * @param priority 操作的优先级, 当多个插件同时做出操作的时候, 框架将选取优先级较高的结果, 优先级相同的
37 | * 情况下随机选择一个操作
38 | */
39 | @JvmStatic fun replacement(value: T, priority: Int = 0): Operation {
40 | return Operation(value = value, priority = priority, returnEarly = true)
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/Version.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | /**
4 | * 用于比较 Android 版本字符串的类
5 | */
6 | class Version(private val versionName: String) {
7 |
8 | private val version: List =
9 | versionName.split('.').mapNotNull(String::toIntOrNull)
10 |
11 | override fun toString() = versionName
12 |
13 | override fun hashCode(): Int = version.hashCode()
14 |
15 | override fun equals(other: Any?): Boolean = when (other) {
16 | null -> false
17 | !is Version -> false
18 | else -> this.version == other.version
19 | }
20 |
21 | operator fun compareTo(other: Version): Int {
22 | var result = 0
23 | when {
24 | this.version.size > other.version.size -> result = 1
25 | this.version.size < other.version.size -> result = -1
26 | }
27 |
28 | var index = 0
29 | while (index < this.version.size && index < other.version.size) {
30 | when {
31 | this.version[index] > other.version[index] -> return 1
32 | this.version[index] < other.version[index] -> return -1
33 | }
34 | index++
35 | }
36 |
37 | return result
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/WaitChannel.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | /**
4 | * 用 Java 实现的一个安全的 Wait Channel, 用来让若干线程安全地阻塞到事件结束
5 | */
6 | class WaitChannel {
7 | @Volatile private var done = false
8 | private val channel = java.lang.Object()
9 |
10 | private val current: Long
11 | get() = System.currentTimeMillis()
12 |
13 | fun wait(timeout: Long = 0L): Boolean {
14 | if (done) return false
15 |
16 | val start = current
17 | synchronized(channel) {
18 | // 处理可能的 spurious wakeup
19 | while (!done && start + timeout > current) {
20 | channel.wait(start + timeout - current)
21 | }
22 | return true
23 | }
24 | }
25 |
26 | fun done() {
27 | if (done) return
28 |
29 | synchronized(channel) {
30 | done = true
31 | channel.notifyAll()
32 | }
33 | }
34 |
35 | fun isDone() = done
36 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/Activities.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.view.Menu
6 | import com.gh0u1l5.wechatmagician.spellbook.C
7 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
8 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
9 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IActivityHook
10 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.Classes.MMActivity
11 | import de.robv.android.xposed.XC_MethodHook
12 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod
13 |
14 | object Activities : EventCenter() {
15 |
16 | override val interfaces: List>
17 | get() = listOf(IActivityHook::class.java)
18 |
19 | override fun provideEventHooker(event: String) = when (event) {
20 | "onMMActivityOptionsMenuCreated" -> onCreateOptionsMenuHooker
21 | "onActivityCreating" -> onCreateHooker
22 | "onActivityStarting" -> onStartHooker
23 | "onActivityResuming" -> onResumeHooker
24 | else -> throw IllegalArgumentException("Unknown event: $event")
25 | }
26 |
27 | private val onCreateOptionsMenuHooker = Hooker {
28 | findAndHookMethod(MMActivity, "onCreateOptionsMenu", C.Menu, object : XC_MethodHook() {
29 | override fun afterHookedMethod(param: MethodHookParam) {
30 | val activity = param.thisObject as? Activity ?: return
31 | val menu = param.args[0] as? Menu ?: return
32 | notify("onMMActivityOptionsMenuCreated") { plugin ->
33 | (plugin as IActivityHook).onMMActivityOptionsMenuCreated(activity, menu)
34 | }
35 | }
36 | })
37 | }
38 |
39 | private val onCreateHooker = Hooker {
40 | findAndHookMethod(C.Activity, "onCreate", C.Bundle, object : XC_MethodHook() {
41 | override fun beforeHookedMethod(param: MethodHookParam) {
42 | val activity = param.thisObject as? Activity ?: return
43 | val savedInstanceState = param.args[0] as Bundle?
44 | notify("onActivityCreating") { plugin ->
45 | (plugin as IActivityHook).onActivityCreating(activity, savedInstanceState)
46 | }
47 | }
48 | })
49 | }
50 |
51 | private val onStartHooker = Hooker {
52 | findAndHookMethod(C.Activity, "onStart", object : XC_MethodHook() {
53 | override fun beforeHookedMethod(param: MethodHookParam) {
54 | val activity = param.thisObject as? Activity ?: return
55 | notify("onActivityStarting") { plugin ->
56 | (plugin as IActivityHook).onActivityStarting(activity)
57 | }
58 | }
59 | })
60 | }
61 |
62 | private val onResumeHooker = Hooker {
63 | findAndHookMethod(C.Activity, "onResume", object : XC_MethodHook() {
64 | override fun beforeHookedMethod(param: MethodHookParam) {
65 | val activity = param.thisObject as? Activity ?: return
66 | notify("onActivityResuming") { plugin ->
67 | (plugin as IActivityHook).onActivityResuming(activity)
68 | }
69 | }
70 | })
71 | }
72 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/Adapters.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import android.widget.BaseAdapter
6 | import com.gh0u1l5.wechatmagician.spellbook.C
7 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
8 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
9 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IAdapterHook
10 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.contact.Classes.AddressAdapter
11 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.conversation.Classes.ConversationWithCacheAdapter
12 | import de.robv.android.xposed.XC_MethodHook
13 | import de.robv.android.xposed.XposedBridge.hookAllConstructors
14 | import de.robv.android.xposed.XposedBridge.log
15 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod
16 |
17 | object Adapters : EventCenter() {
18 |
19 | override val interfaces: List>
20 | get() = listOf(IAdapterHook::class.java)
21 |
22 | override fun provideEventHooker(event: String) = when (event) {
23 | "onAddressAdapterCreated" ->
24 | onAddressAdapterCreateHooker
25 | "onConversationAdapterCreated" ->
26 | onConversationWithCacheAdapterCreateHooker
27 | "onHeaderViewListAdapterGettingView", "onHeaderViewListAdapterGotView" ->
28 | onHeaderViewListAdapterGetViewHooker
29 | else ->
30 | throw IllegalArgumentException("Unknown event: $event")
31 | }
32 |
33 | private val onAddressAdapterCreateHooker = Hooker {
34 | hookAllConstructors(AddressAdapter, object : XC_MethodHook() {
35 | override fun afterHookedMethod(param: MethodHookParam) {
36 | val adapter = param.thisObject as? BaseAdapter
37 | if (adapter == null) {
38 | log("Expect address adapter to be BaseAdapter, get ${param.thisObject::class.java}")
39 | return
40 | }
41 | notify("onAddressAdapterCreated") { plugin ->
42 | (plugin as IAdapterHook).onAddressAdapterCreated(adapter)
43 | }
44 | }
45 | })
46 | }
47 |
48 | private val onConversationWithCacheAdapterCreateHooker = Hooker {
49 | hookAllConstructors(ConversationWithCacheAdapter, object : XC_MethodHook() {
50 | override fun afterHookedMethod(param: MethodHookParam) {
51 | val adapter = param.thisObject as? BaseAdapter
52 | if (adapter == null) {
53 | log("Expect conversation adapter to be BaseAdapter, get ${param.thisObject::class.java}")
54 | return
55 | }
56 | notify("onConversationAdapterCreated") { plugin ->
57 | (plugin as IAdapterHook).onConversationAdapterCreated(adapter)
58 | }
59 | }
60 | })
61 | }
62 |
63 | private val onHeaderViewListAdapterGetViewHooker = Hooker {
64 | findAndHookMethod(
65 | C.HeaderViewListAdapter, "getView",
66 | C.Int, C.View, C.ViewGroup, object : XC_MethodHook() {
67 | override fun beforeHookedMethod(param: MethodHookParam) {
68 | val adapter = param.thisObject
69 | val position = param.args[0] as Int
70 | val convertView = param.args[1] as View?
71 | val parent = param.args[2] as ViewGroup
72 | notifyForOperations("onHeaderViewListAdapterGettingView", param) { plugin ->
73 | (plugin as IAdapterHook).onHeaderViewListAdapterGettingView(adapter, position, convertView, parent)
74 | }
75 | }
76 | override fun afterHookedMethod(param: MethodHookParam) {
77 | val adapter = param.thisObject
78 | val position = param.args[0] as Int
79 | val convertView = param.args[1] as View?
80 | val parent = param.args[2] as ViewGroup
81 | val result = param.result as View?
82 | notifyForOperations("onHeaderViewListAdapterGotView", param) { plugin ->
83 | (plugin as IAdapterHook).onHeaderViewListAdapterGotView(adapter, position, convertView, parent, result)
84 | }
85 | }
86 | })
87 | }
88 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/FileSystem.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
5 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
6 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IFileSystemHook
7 | import de.robv.android.xposed.XC_MethodHook
8 | import de.robv.android.xposed.XposedHelpers.findAndHookConstructor
9 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod
10 | import java.io.File
11 |
12 | object FileSystem : EventCenter() {
13 |
14 | override val interfaces: List>
15 | get() = listOf(IFileSystemHook::class.java)
16 |
17 | override fun provideEventHooker(event: String): Hooker? {
18 | return when (event) {
19 | "onFileDeleting", "onFileDeleted" -> onDeleteHooker
20 | "onFileReading" -> onReadHooker
21 | "onFileWriting" -> onWriteHooker
22 | else -> throw IllegalArgumentException("Unknown event: $event")
23 | }
24 | }
25 |
26 | private val onDeleteHooker = Hooker {
27 | findAndHookMethod(C.File, "delete", object : XC_MethodHook() {
28 | override fun beforeHookedMethod(param: MethodHookParam) {
29 | val file = param.thisObject as File
30 | notifyForOperations("onFileDeleting", param) { plugin ->
31 | (plugin as IFileSystemHook).onFileDeleting(file)
32 | }
33 | }
34 | override fun afterHookedMethod(param: MethodHookParam) {
35 | val file = param.thisObject as File
36 | val result = param.result as Boolean
37 | notifyForOperations("onFileDeleted", param) { plugin ->
38 | (plugin as IFileSystemHook).onFileDeleted(file, result)
39 | }
40 | }
41 | })
42 | }
43 |
44 | private val onReadHooker = Hooker {
45 | findAndHookConstructor(C.FileInputStream, C.File, object : XC_MethodHook() {
46 | override fun beforeHookedMethod(param: MethodHookParam) {
47 | val file = param.args[0] as File
48 | notify("onFileReading") { plugin ->
49 | (plugin as IFileSystemHook).onFileReading(file)
50 | }
51 | }
52 | })
53 | }
54 |
55 | private val onWriteHooker = Hooker {
56 | findAndHookConstructor(C.FileOutputStream, C.File, C.Boolean, object : XC_MethodHook() {
57 | override fun beforeHookedMethod(param: MethodHookParam) {
58 | val file = param.args[0] as File
59 | val append = param.args[1] as Boolean
60 | notify("onFileWriting") { plugin ->
61 | (plugin as IFileSystemHook).onFileWriting(file, append)
62 | }
63 | }
64 | })
65 | }
66 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/Notifications.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import android.os.Message
4 | import com.gh0u1l5.wechatmagician.spellbook.C
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatStatus
6 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
7 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
8 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.INotificationHook
9 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.Classes.MMNotification_MessageHandler
10 | import de.robv.android.xposed.XC_MethodHook
11 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod
12 |
13 | object Notifications : EventCenter() {
14 |
15 | override val interfaces: List>
16 | get() = listOf(INotificationHook::class.java)
17 |
18 | override fun provideEventHooker(event: String): Hooker? {
19 | return when (event) {
20 | "onMessageHandling", "onMessageHandled" -> MessageHandlerHooker
21 | else -> throw IllegalArgumentException("Unknown event: $event")
22 | }
23 | }
24 |
25 | private val MessageHandlerHooker = Hooker {
26 | findAndHookMethod(MMNotification_MessageHandler, "handleMessage", C.Message, object : XC_MethodHook() {
27 | override fun beforeHookedMethod(param: MethodHookParam) {
28 | val raw = param.args[0] as? Message ?: return
29 | val talker = raw.data.getString("notification.show.talker") ?: return
30 | val content = raw.data.getString("notification.show.message.content") ?: return
31 | val type = raw.data.getInt("notification.show.message.type")
32 | val tipsFlag = raw.data.getInt("notification.show.tipsflag")
33 | notifyForBypassFlags("onMessageHandling", param) { plugin ->
34 | (plugin as INotificationHook).onMessageHandling(
35 | INotificationHook.Message(talker, content, type, tipsFlag))
36 | }
37 | }
38 | override fun afterHookedMethod(param: MethodHookParam) {
39 | val raw = param.args[0] as? Message ?: return
40 | val talker = raw.data.getString("notification.show.talker") ?: return
41 | val content = raw.data.getString("notification.show.message.content") ?: return
42 | val type = raw.data.getInt("notification.show.message.type")
43 | val tipsFlag = raw.data.getInt("notification.show.tipsflag")
44 | notify("onMessageHandled") { plugin ->
45 | (plugin as INotificationHook).onMessageHandled(
46 | INotificationHook.Message(talker, content, type, tipsFlag))
47 | }
48 | }
49 | })
50 |
51 | WechatStatus.toggle(WechatStatus.StatusFlag.STATUS_FLAG_NOTIFICATIONS)
52 | }
53 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/SearchBar.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import android.content.Context.INPUT_METHOD_SERVICE
4 | import android.text.Editable
5 | import android.text.TextWatcher
6 | import android.view.inputmethod.InputMethodManager
7 | import android.widget.EditText
8 | import com.gh0u1l5.wechatmagician.spellbook.WechatStatus
9 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
10 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
11 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.ISearchBarConsole
12 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.tools.Classes.ActionBarEditText
13 | import de.robv.android.xposed.XC_MethodHook
14 | import de.robv.android.xposed.XposedBridge.hookAllConstructors
15 |
16 | object SearchBar : EventCenter() {
17 |
18 | override val interfaces: List>
19 | get() = listOf(ISearchBarConsole::class.java)
20 |
21 | override fun provideEventHooker(event: String): Hooker? {
22 | return when (event) {
23 | "onHandleCommand" -> SearchBarHooker
24 | else -> throw IllegalArgumentException("Unknown event: $event")
25 | }
26 | }
27 |
28 | private val SearchBarHooker = Hooker {
29 | hookAllConstructors(ActionBarEditText, object : XC_MethodHook() {
30 | override fun afterHookedMethod(param: MethodHookParam) {
31 | val search = param.thisObject as EditText
32 | search.addTextChangedListener(object : TextWatcher {
33 | override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
34 |
35 | override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
36 |
37 | override fun afterTextChanged(editable: Editable?) {
38 | val context = search.context
39 | var command = editable.toString()
40 | if (command.startsWith("#") && command.endsWith("#")) {
41 | command = command.drop(1).dropLast(1)
42 | notifyParallel("onHandleCommand") { plugin ->
43 | val consumed = (plugin as ISearchBarConsole).onHandleCommand(context, command)
44 | if (consumed) {
45 | // Hide Input Method
46 | val imm = search.context.getSystemService(INPUT_METHOD_SERVICE)
47 | (imm as InputMethodManager).hideSoftInputFromWindow(search.windowToken, 0)
48 | }
49 | }
50 | }
51 | }
52 | })
53 | }
54 | })
55 |
56 | WechatStatus.toggle(WechatStatus.StatusFlag.STATUS_FLAG_COMMAND)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/Storage.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
4 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
5 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IImageStorageHook
6 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IMessageStorageHook
7 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Classes.ImgInfoStorage
8 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Methods.ImgInfoStorage_load
9 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage.Classes.MsgInfoStorage
10 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage.Methods.MsgInfoStorage_insert
11 | import de.robv.android.xposed.XC_MethodHook
12 | import de.robv.android.xposed.XposedBridge.hookAllConstructors
13 | import de.robv.android.xposed.XposedBridge.hookMethod
14 | import de.robv.android.xposed.XposedHelpers.getLongField
15 |
16 | object Storage : EventCenter() {
17 |
18 | override val interfaces: List>
19 | get() = listOf(IMessageStorageHook::class.java, IImageStorageHook::class.java)
20 |
21 | override fun provideEventHooker(event: String): Hooker? {
22 | return when (event) {
23 | "onMessageStorageCreated" -> onMessageStorageCreateHooker
24 | "onMessageStorageInserting", "onMessageStorageInserted" -> onMessageStorageInsertHooker
25 | "onImageStorageCreated" -> onImageStorageCreateHooker
26 | "onImageStorageLoading", "onImageStorageLoaded" -> onImageStorageLoadHooker
27 | else -> throw IllegalArgumentException("Unknown event: $event")
28 | }
29 | }
30 |
31 | private val onMessageStorageCreateHooker = Hooker {
32 | hookAllConstructors(MsgInfoStorage, object : XC_MethodHook() {
33 | override fun afterHookedMethod(param: MethodHookParam) {
34 | notify("onMessageStorageCreated") { plugin ->
35 | (plugin as IMessageStorageHook).onMessageStorageCreated(param.thisObject)
36 | }
37 | }
38 | })
39 | }
40 |
41 | private val onMessageStorageInsertHooker = Hooker {
42 | hookMethod(MsgInfoStorage_insert, object : XC_MethodHook() {
43 | override fun beforeHookedMethod(param: MethodHookParam) {
44 | val msgObject = param.args[0]
45 | val msgId = getLongField(msgObject, "field_msgId")
46 | notifyForBypassFlags("onMessageStorageInserting", param) { plugin ->
47 | (plugin as IMessageStorageHook).onMessageStorageInserting(msgId, msgObject)
48 | }
49 | }
50 | override fun afterHookedMethod(param: MethodHookParam) {
51 | val msgObject = param.args[0]
52 | val msgId = getLongField(msgObject, "field_msgId")
53 | notify("onMessageStorageInserted") { plugin ->
54 | (plugin as IMessageStorageHook).onMessageStorageInserted(msgId, msgObject)
55 | }
56 | }
57 | })
58 | }
59 |
60 | private val onImageStorageCreateHooker = Hooker {
61 | hookAllConstructors(ImgInfoStorage, object : XC_MethodHook() {
62 | override fun afterHookedMethod(param: MethodHookParam) {
63 | notify("onImageStorageCreated") { plugin ->
64 | (plugin as IImageStorageHook).onImageStorageCreated(param.thisObject)
65 | }
66 | }
67 | })
68 | }
69 |
70 | private val onImageStorageLoadHooker = Hooker {
71 | hookMethod(ImgInfoStorage_load, object : XC_MethodHook() {
72 | override fun beforeHookedMethod(param: MethodHookParam) {
73 | val imageId = param.args[0] as String?
74 | val prefix = param.args[1] as String?
75 | val suffix = param.args[2] as String?
76 | notifyForBypassFlags("onImageStorageLoading", param) { plugin ->
77 | (plugin as IImageStorageHook).onImageStorageLoading(imageId, prefix, suffix)
78 | }
79 | }
80 | override fun afterHookedMethod(param: MethodHookParam) {
81 | val imageId = param.args[0] as String?
82 | val prefix = param.args[1] as String?
83 | val suffix = param.args[2] as String?
84 | notify("onImageStorageLoaded") { plugin ->
85 | (plugin as IImageStorageHook).onImageStorageLoaded(imageId, prefix, suffix)
86 | }
87 | }
88 | })
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/UriRouter.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatStatus
6 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
7 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
8 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IUriRouterHook
9 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.base.stub.Methods.WXCustomScheme_entry
10 | import de.robv.android.xposed.XC_MethodHook
11 | import de.robv.android.xposed.XposedBridge.hookMethod
12 |
13 | object UriRouter : EventCenter() {
14 |
15 | override val interfaces: List>
16 | get() = listOf(IUriRouterHook::class.java)
17 |
18 | override fun provideEventHooker(event: String): Hooker? {
19 | return when (event) {
20 | "onReceiveUri" -> UriRouterHooker
21 | else -> throw IllegalArgumentException("Unknown event: $event")
22 | }
23 | }
24 |
25 | private val UriRouterHooker = Hooker {
26 | hookMethod(WXCustomScheme_entry, object : XC_MethodHook() {
27 | override fun beforeHookedMethod(param: MethodHookParam) {
28 | val intent = param.args[0] as Intent?
29 | val uri = intent?.data ?: return
30 | val activity = param.thisObject as Activity
31 | if (uri.host == "magician") {
32 | notifyParallel("onReceiveUri") { plugin ->
33 | (plugin as IUriRouterHook).onReceiveUri(activity, uri)
34 | }
35 | param.result = false
36 | }
37 | }
38 | })
39 |
40 | WechatStatus.toggle(WechatStatus.StatusFlag.STATUS_FLAG_URI_ROUTER)
41 | }
42 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/XLog.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
4 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
5 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IXLogHook
6 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog.Constants.toHumanReadableLevel
7 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog.Methods.Xlog_logWrite2
8 | import de.robv.android.xposed.XC_MethodHook
9 | import de.robv.android.xposed.XposedBridge.hookMethod
10 |
11 | object XLog : EventCenter() {
12 | override val interfaces: List>
13 | get() = listOf(IXLogHook::class.java)
14 |
15 | override fun provideEventHooker(event: String) = when (event) {
16 | "onXLogWrite" -> onXLogWriteHooker
17 | else -> throw IllegalArgumentException("Unknown event: $event")
18 | }
19 |
20 | private val onXLogWriteHooker = Hooker {
21 | hookMethod(Xlog_logWrite2, object : XC_MethodHook() {
22 | override fun afterHookedMethod(param: MethodHookParam) {
23 | val level = toHumanReadableLevel(param.args[0] as Int)
24 | val tag = param.args[1] as String? ?: ""
25 | val msg = param.args[8] as String? ?: ""
26 | notifyParallel("onXLogWrite", { plugin ->
27 | (plugin as IXLogHook).onXLogWrite(level, tag, msg)
28 | })
29 | }
30 | })
31 | }
32 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/hookers/XmlParser.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.hookers
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatStatus
4 | import com.gh0u1l5.wechatmagician.spellbook.base.EventCenter
5 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
6 | import com.gh0u1l5.wechatmagician.spellbook.interfaces.IXmlParserHook
7 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools.Methods.XmlParser_parse
8 | import de.robv.android.xposed.XC_MethodHook
9 | import de.robv.android.xposed.XposedBridge.hookMethod
10 |
11 | object XmlParser : EventCenter() {
12 |
13 | override val interfaces: List>
14 | get() = listOf(IXmlParserHook::class.java)
15 |
16 | override fun provideEventHooker(event: String): Hooker? {
17 | return when (event) {
18 | "onXmlParsing", "onXmlParsed" -> onXmlParseHooker
19 | else -> throw IllegalArgumentException("Unknown event: $event")
20 | }
21 | }
22 |
23 | private val onXmlParseHooker = Hooker {
24 | hookMethod(XmlParser_parse, object : XC_MethodHook() {
25 | override fun beforeHookedMethod(param: MethodHookParam) {
26 | val xml = param.args[0] as String
27 | val root = param.args[1] as String
28 | notifyForOperations("onXmlParsing", param) { plugin ->
29 | (plugin as IXmlParserHook).onXmlParsing(xml, root)
30 | }
31 | }
32 | @Suppress("UNCHECKED_CAST")
33 | override fun afterHookedMethod(param: MethodHookParam) {
34 | val xml = param.args[0] as String
35 | val root = param.args[1] as String
36 | val result = param.result as MutableMap? ?: return
37 | notify("onXmlParsed") { plugin ->
38 | (plugin as IXmlParserHook).onXmlParsed(xml, root, result)
39 | }
40 | }
41 | })
42 |
43 | WechatStatus.toggle(WechatStatus.StatusFlag.STATUS_FLAG_XML_PARSER)
44 | }
45 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IActivityHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import android.view.Menu
6 |
7 | interface IActivityHook {
8 |
9 | /**
10 | * Called when a Wechat MMActivity has created a options menu.
11 | *
12 | * @param activity the activity shown in foreground.
13 | * @param menu the options menu just created by the activity.
14 | */
15 | fun onMMActivityOptionsMenuCreated(activity: Activity, menu: Menu) { }
16 |
17 | /**
18 | * Called when an Activity is going to invoke [Activity.onCreate] method.
19 | *
20 | * @param activity the activity object that is creating.
21 | * @param savedInstanceState the saved instance state for restoring the state.
22 | */
23 | fun onActivityCreating(activity: Activity, savedInstanceState: Bundle?) { }
24 |
25 | /**
26 | * Called when an activity is going to invoke [Activity.onStart] method.
27 | *
28 | * @param activity the activity object that is starting.
29 | */
30 | fun onActivityStarting(activity: Activity) { }
31 |
32 | /**
33 | * Called when an activity is going to invoke [Activity.onResume] method.
34 | *
35 | * @param activity the activity object that is resuming.
36 | */
37 | fun onActivityResuming(activity: Activity) { }
38 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IAdapterHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import android.widget.Adapter
6 | import android.widget.BaseAdapter
7 | import com.gh0u1l5.wechatmagician.spellbook.base.Operation
8 | import com.gh0u1l5.wechatmagician.spellbook.base.Operation.Companion.nop
9 |
10 | interface IAdapterHook {
11 |
12 | /**
13 | * Called when a Wechat AddressAdapter has been created. This adapter will be used in the
14 | * ListView for all the contacts (which is shown in the second tab of Wechat).
15 | *
16 | * @param adapter the created AddressAdapter object.
17 | */
18 | fun onAddressAdapterCreated(adapter: BaseAdapter) { }
19 |
20 | /**
21 | * Called when a Wechat ConversationAdapter has been created. This adapter will be used in the
22 | * ListView for all the conversations (which is shown in the first tab of Wechat).
23 | *
24 | * @param adapter the created ConversationAdapter object.
25 | */
26 | fun onConversationAdapterCreated(adapter: BaseAdapter) { }
27 |
28 | /**
29 | * Called when an HeaderViewListAdapter object is going to invoke [Adapter.getView] method.
30 | *
31 | * @param adapter the HeaderViewListAdapter object calling the getView().
32 | * @param position the position of the item whose view we want.
33 | * @param convertView the old view to reuse, if possible.
34 | * @param parent the parent that this view will eventually be attached to.
35 | * @return to bypass the original method, return a View object wrapped by [Operation.replacement]
36 | * or a throwable wrapped by [Operation.interruption], otherwise return [Operation.nop].
37 | */
38 | fun onHeaderViewListAdapterGettingView(adapter: Any, position: Int, convertView: View?, parent: ViewGroup): Operation = nop()
39 |
40 | /**
41 | * Called when an HeaderViewListAdapter object has returned from [Adapter.getView] method.
42 | *
43 | * @param adapter the HeaderViewListAdapter object calling the getView().
44 | * @param position the position of the item whose view we want.
45 | * @param convertView the old view to reuse, if possible.
46 | * @param parent the parent that this view will eventually be attached to.
47 | * @param result the view that is returned by the original getView() function.
48 | * @return to replace the original result, return a View object wrapped by [Operation.replacement],
49 | * otherwise return [Operation.nop].
50 | */
51 | fun onHeaderViewListAdapterGotView(adapter: Any, position: Int, convertView: View?, parent: ViewGroup, result: View?): Operation = nop()
52 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IFileSystemHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.base.Operation
4 | import com.gh0u1l5.wechatmagician.spellbook.base.Operation.Companion.nop
5 | import java.io.File
6 | import java.io.FileInputStream
7 | import java.io.FileOutputStream
8 |
9 | interface IFileSystemHook {
10 |
11 | /**
12 | * Called when a File object is going to invoke [File.delete]
13 | *
14 | * @param file the [File] object calling [File.delete]
15 | * @return to bypass the original method, return a Boolean wrapped by [Operation.replacement],
16 | * otherwise return [Operation.nop].
17 | */
18 | fun onFileDeleting(file: File): Operation = nop()
19 |
20 | /**
21 | * Called when a File object has returned from [File.delete]
22 | *
23 | * @param file the [File] object calling [File.delete]
24 | * @param result the result returned by [File.delete]
25 | * @return to replace the original result, return a Boolean wrapped by [Operation.replacement],
26 | * otherwise return [Operation.nop].
27 | */
28 | fun onFileDeleted(file: File, result: Boolean): Operation = nop()
29 |
30 | /**
31 | * Called when a [FileInputStream] object is being created.
32 | *
33 | * @param file the [File] object the stream reading from.
34 | */
35 | fun onFileReading(file: File) { }
36 |
37 | /**
38 | * Called when a [FileOutputStream] object is being created.
39 | *
40 | * @param file the [File] object the stream writing to
41 | * @param append if `true`, then bytes will be written to the end rather than the beginning
42 | */
43 | fun onFileWriting(file: File, append: Boolean) { }
44 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IImageStorageHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | interface IImageStorageHook {
4 |
5 | /**
6 | * Called when an ImgInfoStorage object has been created.
7 | *
8 | * @param storage the ImgInfoStorage object created by a constructor.
9 | */
10 | fun onImageStorageCreated(storage: Any) { }
11 |
12 | /**
13 | * Called when an ImgInfoStorage is loading a new image information.
14 | *
15 | * @param imageId the internal ID of the image.
16 | * @param prefix
17 | * @param suffix
18 | * @return to bypass the original method, return `true`, otherwise return `false`.
19 | */
20 | fun onImageStorageLoading(imageId: String?, prefix: String?, suffix: String?) = false
21 |
22 | /**
23 | * Called when an ImgInfoStorage has loaded a new image information.
24 | *
25 | * @param imageId the internal ID of the image.
26 | * @param prefix
27 | * @param suffix
28 | */
29 | fun onImageStorageLoaded(imageId: String?, prefix: String?, suffix: String?) { }
30 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IMessageStorageHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | interface IMessageStorageHook {
4 |
5 | /**
6 | * Called when an MsgInfoStorage object has been created.
7 | *
8 | * @param storage the MsgInfoStorage object created by a constructor.
9 | */
10 | fun onMessageStorageCreated(storage: Any) { }
11 |
12 | /**
13 | * Called when an MsgInfoStorage is inserting a new message.
14 | *
15 | * @param msgId the internal ID of the message.
16 | * @param msgObject the message object stored into storage.
17 | * @return to bypass the original method, return `true`, otherwise return `false`.
18 | */
19 | fun onMessageStorageInserting(msgId: Long, msgObject: Any) = false
20 |
21 | /**
22 | * Called when an MsgInfoStorage has inserted a new message.
23 | *
24 | * @param msgId the internal ID of the message.
25 | * @param msgObject the message object stored into storage.
26 | */
27 | fun onMessageStorageInserted(msgId: Long, msgObject: Any) { }
28 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/INotificationHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | interface INotificationHook {
4 |
5 | /**
6 | * The bean class that describes a notification message.
7 | */
8 | data class Message(
9 | val talker: String,
10 | val content: String,
11 | val type: Int,
12 | val tipsFlag: Int
13 | )
14 |
15 | /**
16 | * Called when the global message handler is going to handle a [Message].
17 | *
18 | * @param message the message received by the handler.
19 | * @return to prevent showing the message, return `true`, otherwise return `false`
20 | */
21 | fun onMessageHandling(message: Message) = false
22 |
23 | /**
24 | * Called when the global message handler has handled a [Message].
25 | *
26 | * @param message the message received by the handler.
27 | */
28 | fun onMessageHandled(message: Message) { }
29 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IPopupMenuHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.hookers.MenuAppender
4 |
5 | interface IPopupMenuHook {
6 |
7 | /**
8 | * Called when the popup menu for a contact is going to be created.
9 | *
10 | * @param username the username of the contact that has been long clicking.
11 | * @return to add your own menu items, return a list of [MenuAppender.PopupMenuItem], otherwise
12 | * return null.
13 | */
14 | fun onPopupMenuForContactsCreating(username: String): List? = null
15 |
16 | /**
17 | * Called when the popup menu for a conversation is going to be created.
18 | *
19 | * @param username the username of the conversation that has been long clicking.
20 | * @return to add your own menu items, return a list of [MenuAppender.PopupMenuItem], otherwise
21 | * return null.
22 | */
23 | fun onPopupMenuForConversationsCreating(username: String): List? = null
24 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/ISearchBarConsole.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import android.content.Context
4 |
5 | interface ISearchBarConsole {
6 | /**
7 | * Called when the user entered a command in the search bar.
8 | *
9 | * @param context a [Context] that can be used for later operations.
10 | * @param command the commend entered by the user.
11 | * @return true if the command should be consumed, otherwise return false.
12 | */
13 | fun onHandleCommand(context: Context, command: String) = false
14 | }
15 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IUriRouterHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.net.Uri
6 |
7 | interface IUriRouterHook {
8 | /**
9 | * Called when the URI router get a new request
10 | *
11 | * @param activity an activity that can be used as a [Context].
12 | * @param uri the uri sent from the other applications. It should start with "weixin://magician/".
13 | */
14 | fun onReceiveUri(activity: Activity, uri: Uri) { }
15 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IXLogHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | interface IXLogHook {
4 | /**
5 | * Called when Tencent XLog has written some log information.
6 | *
7 | * @param level a human readable log level.
8 | * @param tag a human readable log tag.
9 | * @param msg the message printed to the output.
10 | */
11 | fun onXLogWrite(level: String, tag: String, msg: String) { }
12 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/interfaces/IXmlParserHook.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.interfaces
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.base.Operation
4 | import com.gh0u1l5.wechatmagician.spellbook.base.Operation.Companion.nop
5 |
6 | interface IXmlParserHook {
7 |
8 | /**
9 | * Called when the XML parser is going to parse a XML string.
10 | *
11 | * @param xml the XML string given by the caller.
12 | * @param root the tag name of the section the caller want to parse.
13 | * @return to bypass the original method, return a MutableMap object wrapped by
14 | * [Operation.replacement], or a throwable wrapped by [Operation.interruption], otherwise return
15 | * [Operation.nop].
16 | */
17 | fun onXmlParsing(xml: String, root: String): Operation?> = nop()
18 |
19 | /**
20 | * Called when the XML parser has parsed a XML string.
21 | *
22 | * @param xml the XML string given by the caller.
23 | * @param root the tag name of the section the caller want to parse.
24 | * @param result the map generated from the XML string.
25 | */
26 | fun onXmlParsed(xml: String, root: String, result: MutableMap) { }
27 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/MirrorClasses.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror
2 |
3 | /**
4 | * Dynamically generated by build.gradle
5 | */
6 | val MirrorClasses = listOf (
7 | com.gh0u1l5.wechatmagician.spellbook.mirror.android.support.v4.app.Classes,
8 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog.Classes,
9 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Classes,
10 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.Classes,
11 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.queue.Classes,
12 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.modelsfs.Classes,
13 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.base.stub.Classes,
14 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.gallery.ui.Classes,
15 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.remittance.ui.Classes,
16 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.sns.ui.Classes,
17 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.webwx.ui.Classes,
18 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools.Classes,
19 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage.Classes,
20 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.Classes,
21 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.base.Classes,
22 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.chatting.Classes,
23 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.contact.Classes,
24 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.conversation.Classes,
25 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.tools.Classes,
26 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.transmit.Classes,
27 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.Classes,
28 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.database.Classes,
29 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.support.Classes
30 | )
31 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/MirrorFields.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror
2 |
3 | /**
4 | * Dynamically generated by build.gradle
5 | */
6 | val MirrorFields = listOf (
7 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Fields,
8 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.sns.ui.Fields
9 | )
10 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/MirrorMethods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror
2 |
3 | /**
4 | * Dynamically generated by build.gradle
5 | */
6 | val MirrorMethods = listOf (
7 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog.Methods,
8 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Methods,
9 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.queue.Methods,
10 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.modelsfs.Methods,
11 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.base.stub.Methods,
12 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools.Methods,
13 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage.Methods,
14 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.Methods,
15 | com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.transmit.Methods
16 | )
17 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/android/support/v4/app/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.android.support.v4.app
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
7 |
8 | object Classes {
9 | val NotificationManagerCompat: Class<*> by wxLazy("NotificationManagerCompat") {
10 | // v4切换成 androidx 兼容包
11 | findClassesFromPackage(wxLoader!!, wxClasses!!, "androidx.core.app")
12 | .filterByField("android.app.NotificationManager")
13 | .firstOrNull()
14 | }
15 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mars/xlog/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
6 |
7 | /**
8 | * 路径正确
9 | */
10 | object Classes {
11 | val Xlog: Class<*> by wxLazy("Xlog") {
12 | findClassIfExists("com.tencent.mars.xlog.Xlog", wxLoader!!)
13 | }
14 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mars/xlog/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog
2 |
3 | /**
4 | * 常量正确
5 | */
6 | object Constants {
7 | const val Xlog_AppenderModeAsync = 0
8 | const val Xlog_AppenderModeSync = 1
9 |
10 | const val Xlog_LEVEL_ALL = 0
11 | const val Xlog_LEVEL_VERBOSE = 0
12 | const val Xlog_LEVEL_DEBUG = 1
13 | const val Xlog_LEVEL_INFO = 2
14 | const val Xlog_LEVEL_WARNING = 3
15 | const val Xlog_LEVEL_ERROR = 4
16 | const val Xlog_LEVEL_FATAL = 5
17 | const val Xlog_LEVEL_NONE = 6
18 |
19 | fun toHumanReadableLevel(level: Int): String = when (level) {
20 | Xlog_LEVEL_VERBOSE -> "VERBOSE"
21 | Xlog_LEVEL_DEBUG -> "DEBUG"
22 | Xlog_LEVEL_INFO -> "INFO"
23 | Xlog_LEVEL_WARNING -> "WARNING"
24 | Xlog_LEVEL_ERROR -> "ERROR"
25 | Xlog_LEVEL_FATAL -> "FATAL"
26 | Xlog_LEVEL_NONE -> "NONE"
27 | else -> "UNKNOWN($level)"
28 | }
29 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mars/xlog/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mars.xlog.Classes.Xlog
5 | import java.lang.reflect.Method
6 |
7 | /**
8 | * 方法正确
9 | */
10 | object Methods {
11 | val Xlog_logWrite: Method by wxLazy("Xlog_logWrite") {
12 | Xlog.declaredMethods.find { it.name == "logWrite" }
13 | }
14 |
15 | val Xlog_logWrite2: Method by wxLazy("Xlog_logWrite2") {
16 | Xlog.declaredMethods.find { it.name == "logWrite2" }
17 | }
18 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools.Classes.LruCache
9 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
10 |
11 | object Classes {
12 | val ImgInfoStorage: Class<*> by wxLazy("ImgInfoStorage") {
13 | findClassesFromPackage(wxLoader!!, wxClasses!!, wxPackageName, 1)
14 | .filterByMethod(C.String, C.String, C.String, C.String, C.Boolean)
15 | .firstOrNull()
16 | }
17 |
18 | val LruCacheWithListener: Class<*> by wxLazy("LruCacheWithListener") {
19 | findClassesFromPackage(wxLoader!!, wxClasses!!, wxPackageName, 1)
20 | .filterBySuper(LruCache)
21 | .firstOrNull()
22 | }
23 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/Fields.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Classes.ImgInfoStorage
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Classes.LruCacheWithListener
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findFieldsWithGenericType
7 | import java.lang.reflect.Field
8 |
9 | object Fields {
10 | val ImgInfoStorage_mBitmapCache: Field by wxLazy("ImgInfoStorage_mBitmapCache") {
11 | findFieldsWithGenericType(
12 | ImgInfoStorage, "${LruCacheWithListener.canonicalName}")
13 | .firstOrNull()?.apply { isAccessible = true }
14 | }
15 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Classes.ImgInfoStorage
6 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.Classes.LruCacheWithListener
7 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
8 | import java.lang.reflect.Method
9 |
10 | object Methods {
11 | val ImgInfoStorage_load: Method by wxLazy("ImgInfoStorage_load") {
12 | findMethodsByExactParameters(ImgInfoStorage, C.String, C.String, C.String, C.String, C.Boolean)
13 | .firstOrNull()?.apply { isAccessible = true }
14 | }
15 |
16 | val LruCacheWithListener_put: Method by wxLazy("LruCacheWithListener_put") {
17 | findMethodsByExactParameters(LruCacheWithListener, null, C.Object, C.Object)
18 | .firstOrNull()?.apply { isAccessible = true }
19 | }
20 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/booter/notification/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
9 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
10 |
11 | object Classes {
12 |
13 | val MMNotification: Class<*> by wxLazy("MMNotification") {
14 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.booter.notification")
15 | .filterByField("talker")
16 | .firstOrNull()
17 | }
18 |
19 | val MMNotification_MessageHandler: Class<*> by wxLazy("MMNotification_MessageHandler") {
20 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.booter.notification")
21 | .filterByMethod(null, "handleMessage", C.Message)
22 | .firstOrNull()
23 | }
24 |
25 | val NotificationItem: Class<*> by wxLazy("NotificationItem") {
26 | findClassIfExists("$wxPackageName.booter.notification.NotificationItem", wxLoader!!)
27 | }
28 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/booter/notification/queue/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.queue
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.Classes.NotificationItem
9 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
10 |
11 | object Classes {
12 | val NotificationAppMsgQueue: Class<*> by wxLazy("NotificationAppMsgQueue") {
13 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.booter.notification.queue")
14 | .filterByMethod(null, NotificationItem)
15 | .filterByMethod(C.Iterator, "iterator")
16 | .firstOrNull()
17 | }
18 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/booter/notification/queue/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.queue
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.Classes.NotificationItem
6 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.booter.notification.queue.Classes.NotificationAppMsgQueue
7 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
8 | import java.lang.reflect.Method
9 |
10 | object Methods {
11 | val NotificationAppMsgQueue_add: Method by wxLazy("NotificationAppMsgQueue_add") {
12 | findMethodsByExactParameters(NotificationAppMsgQueue, null, NotificationItem)
13 | .firstOrNull()?.apply { isAccessible = true }
14 | }
15 | val NotificationAppMsgQueue_remove: Method by wxLazy("NotificationAppMsgQueue_remove") {
16 | findMethodsByExactParameters(NotificationAppMsgQueue, C.Boolean, C.String)
17 | .firstOrNull()?.apply { isAccessible = true }
18 | }
19 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/modelsfs/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.modelsfs
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
9 |
10 | object Classes {
11 | val EncEngine: Class<*> by wxLazy("EncEngine") {
12 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.modelsfs")
13 | .filterByMethod(null, "seek", C.Long)
14 | .filterByMethod(null, "free")
15 | .firstOrNull()
16 | }
17 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/modelsfs/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.modelsfs
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.modelsfs.Classes.EncEngine
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
7 | import java.lang.reflect.Method
8 |
9 | object Methods {
10 | val EncEngine_transFor: Method by wxLazy("EncEngine_transFor") {
11 | findMethodsByExactParameters(EncEngine, C.Int, C.ByteArray, C.Int).firstOrNull()
12 | }
13 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/base/stub/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.base.stub
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | val WXCustomScheme: Class<*> by wxLazy("WXCustomScheme") {
10 | findClassIfExists("$wxPackageName.plugin.base.stub.WXCustomSchemeEntryActivity", wxLoader!!)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/base/stub/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.base.stub
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.base.stub.Classes.WXCustomScheme
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
7 | import java.lang.reflect.Method
8 |
9 | object Methods {
10 | val WXCustomScheme_entry: Method by wxLazy("WXCustomScheme_entry") {
11 | findMethodsByExactParameters(WXCustomScheme, C.Boolean, C.Intent).firstOrNull()
12 | }
13 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/gallery/ui/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.gallery.ui
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | /**
10 | * 当用户需要从手机相册中选择照片时启动的Activity
11 | */
12 | val AlbumPreviewUI: Class<*> by wxLazy("AlbumPreviewUI") {
13 | findClassIfExists("$wxPackageName.plugin.gallery.ui.AlbumPreviewUI", wxLoader!!)
14 | }
15 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/remittance/ui/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.remittance.ui
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | val RemittanceAdapter: Class<*> by wxLazy("RemittanceAdapter") {
10 | findClassIfExists("$wxPackageName.plugin.remittance.ui.RemittanceAdapterUI", wxLoader!!)
11 | }
12 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/sns/ui/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.sns.ui
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
7 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
9 |
10 | object Classes {
11 | val SnsActivity: Class<*> by wxLazy("SnsActivity") {
12 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.plugin.sns.ui")
13 | .filterByField("$wxPackageName.ui.base.MMPullDownView")
14 | .firstOrNull()
15 | }
16 |
17 | val SnsTimeLineUI: Class<*> by wxLazy("SnsTimeLineUI") {
18 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.plugin.sns.ui")
19 | .filterByField("android.support.v7.app.ActionBar")
20 | .firstOrNull()
21 | }
22 |
23 | val SnsUploadUI: Class<*> by wxLazy("SnsUploadUI") {
24 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.plugin.sns.ui")
25 | .filterByField("$wxPackageName.plugin.sns.ui.LocationWidget")
26 | .filterByField("$wxPackageName.plugin.sns.ui.SnsUploadSayFooter")
27 | .firstOrNull()
28 | }
29 |
30 | val SnsUserUI: Class<*> by wxLazy("SnsUserUI") {
31 | findClassIfExists("$wxPackageName.plugin.sns.ui.SnsUserUI", wxLoader!!)
32 | }
33 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/sns/ui/Fields.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.sns.ui
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.sns.ui.Classes.SnsUploadUI
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findFieldsWithType
7 | import java.lang.reflect.Field
8 |
9 | object Fields {
10 | val SnsUploadUI_mSnsEditText: Field by wxLazy("SnsUploadUI_mSnsEditText") {
11 | findFieldsWithType(SnsUploadUI, "$wxPackageName.plugin.sns.ui.SnsEditText")
12 | .firstOrNull()?.apply { isAccessible = true }
13 | }
14 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/plugin/webwx/ui/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.plugin.webwx.ui
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | val ExtDeviceWXLoginUI: Class<*> by wxLazy("ExtDeviceWXLoginUI") {
10 | findClassIfExists("$wxPackageName.plugin.webwx.ui.ExtDeviceWXLoginUI", wxLoader!!)
11 | }
12 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/sdk/platformtools/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
9 |
10 | object Classes {
11 | val Logcat: Class<*> by wxLazy("Logcat") {
12 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.sdk.platformtools")
13 | .filterByEnclosingClass(null)
14 | .filterByMethod(C.Int, "getLogLevel")
15 | .firstOrNull()
16 | }
17 |
18 | val LruCache: Class<*> by wxLazy("LruCache") {
19 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.sdk.platformtools")
20 | .filterByMethod(null, "trimToSize", C.Int)
21 | .firstOrNull()
22 | }
23 |
24 | val XmlParser: Class<*> by wxLazy("XmlParser") {
25 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.sdk.platformtools")
26 | .filterByMethod(C.Map, C.String, C.String)
27 | .firstOrNull()
28 | }
29 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/sdk/platformtools/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.sdk.platformtools.Classes.XmlParser
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
7 | import java.lang.reflect.Method
8 |
9 | object Methods {
10 | val XmlParser_parse: Method by wxLazy("XmlParser_parse") {
11 | findMethodsByExactParameters(XmlParser, C.Map, C.String, C.String).firstOrNull()
12 | }
13 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/storage/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
9 |
10 | object Classes {
11 | val MsgInfo: Class<*> by wxLazy("MsgInfo") {
12 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.storage")
13 | .filterByMethod(C.Boolean, "isSystem")
14 | .firstOrNull()
15 | }
16 |
17 | val MsgInfoStorage: Class<*> by wxLazy("MsgInfoStorage") {
18 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.storage")
19 | .filterByMethod(C.Long, MsgInfo, C.Boolean)
20 | .firstOrNull()
21 | }
22 |
23 | val ContactInfo: Class<*> by wxLazy("ContactInfo") {
24 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.storage")
25 | .filterByMethod(C.String, "getCityCode")
26 | .filterByMethod(C.String, "getCountryCode")
27 | .firstOrNull()
28 | }
29 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/storage/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage.Classes.MsgInfo
6 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.storage.Classes.MsgInfoStorage
7 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
8 | import java.lang.reflect.Method
9 |
10 | object Methods {
11 | val MsgInfoStorage_insert: Method by wxLazy("MsgInfoStorage_insert") {
12 | findMethodsByExactParameters(MsgInfoStorage, C.Long, MsgInfo, C.Boolean).firstOrNull()
13 | }
14 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui
2 |
3 | import android.widget.BaseAdapter
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
9 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
10 |
11 | object Classes {
12 | val LauncherUI: Class<*> by wxLazy("LauncherUI") {
13 | findClassIfExists("$wxPackageName.ui.LauncherUI", wxLoader!!)
14 | }
15 |
16 | val MMActivity: Class<*> by wxLazy("MMActivity") {
17 | findClassIfExists("$wxPackageName.ui.MMActivity", wxLoader!!)
18 | }
19 |
20 | val MMFragmentActivity: Class<*> by wxLazy("MMFragmentActivity") {
21 | findClassIfExists("$wxPackageName.ui.MMFragmentActivity", wxLoader!!)
22 | }
23 |
24 | val MMBaseAdapter: Class<*> by wxLazy("MMBaseAdapter") {
25 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui")
26 | .filterBySuper(BaseAdapter::class.java)
27 | .filterByField("TAG", "java.lang.String")
28 | .firstOrNull()
29 | }
30 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.Classes.MMBaseAdapter
6 |
7 | object Methods {
8 | val MMBaseAdapter_getItemInternal: String by wxLazy("MMBaseAdapter_getItemInternal") {
9 | MMBaseAdapter.declaredMethods.filter {
10 | it.parameterTypes.size == 1 && it.parameterTypes[0] == C.Int
11 | }.firstOrNull {
12 | it.name != "getItem" && it.name != "getItemId"
13 | }?.name
14 | }
15 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/base/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.base
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | val MMListPopupWindow: Class<*> by wxLazy("MMListPopupWindow") {
10 | findClassIfExists("$wxPackageName.ui.base.MMListPopupWindow", wxLoader!!)
11 | }
12 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/chatting/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.chatting
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.Classes.MMFragmentActivity
9 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
10 |
11 | object Classes {
12 | val ChattingUI: Class<*> by wxLazy("ChattingUI") {
13 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui.chatting")
14 | .filterBySuper(MMFragmentActivity)
15 | .filterByMethod(null, "onRequestPermissionsResult", C.Int, C.StringArray, C.IntArray)
16 | .firstOrNull()
17 | }
18 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/contact/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.contact
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
9 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
10 |
11 | object Classes {
12 | val AddressUI: Class<*> by wxLazy("AddressUI") {
13 | findClassIfExists("$wxPackageName.ui.contact.AddressUI\$a", wxLoader!!)
14 | }
15 |
16 | val AddressAdapter: Class<*> by wxLazy("AddressAdapter") {
17 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui.contact")
18 | .filterByMethod(null, "pause")
19 | .firstOrNull()
20 | }
21 |
22 | val ContactLongClickListener: Class<*> by wxLazy("ContactLongClickListener") {
23 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui.contact")
24 | .filterByEnclosingClass(AddressUI)
25 | .filterByMethod(C.Boolean, "onItemLongClick", C.AdapterView, C.View, C.Int, C.Long)
26 | .firstOrNull()
27 | }
28 |
29 | val SelectContactUI: Class<*> by wxLazy("SelectContactUI") {
30 | findClassIfExists("$wxPackageName.ui.contact.SelectContactUI", wxLoader!!)
31 | }
32 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/conversation/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.conversation
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxClasses
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
6 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
7 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
8 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassesFromPackage
9 |
10 | object Classes {
11 | val ConversationWithCacheAdapter: Class<*> by wxLazy("ConversationWithCacheAdapter") {
12 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui.conversation")
13 | .filterByMethod(null, "clearCache")
14 | .firstOrNull()
15 | }
16 |
17 | val ConversationCreateContextMenuListener: Class<*> by wxLazy("ConversationCreateContextMenuListener") {
18 | ConversationLongClickListener
19 | }
20 |
21 | val ConversationLongClickListener: Class<*> by wxLazy("ConversationLongClickListener") {
22 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui.conversation")
23 | .filterByMethod(null, "onCreateContextMenu", C.ContextMenu, C.View, C.ContextMenuInfo)
24 | .filterByMethod(C.Boolean, "onItemLongClick", C.AdapterView, C.View, C.Int, C.Long)
25 | .firstOrNull()
26 | }
27 |
28 | val MainUI: Class<*> by wxLazy("MainUI") {
29 | findClassesFromPackage(wxLoader!!, wxClasses!!, "$wxPackageName.ui.conversation")
30 | .filterByMethod(C.Int, "getLayoutId")
31 | .filterByMethod(null, "onConfigurationChanged", C.Configuration)
32 | .firstOrNull()
33 | }
34 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/tools/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.tools
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | val ActionBarEditText: Class<*> by wxLazy("ActionBarEditText") {
10 | findClassIfExists("$wxPackageName.ui.tools.ActionBarSearchView\$ActionBarEditText", wxLoader!!)
11 | }
12 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/transmit/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.transmit
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxPackageName
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | object Classes {
9 | val SelectConversationUI: Class<*> by wxLazy("SelectConversationUI") {
10 | findClassIfExists("$wxPackageName.ui.transmit.SelectConversationUI", wxLoader!!)
11 | }
12 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/mm/ui/transmit/Methods.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.transmit
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.C
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.mm.ui.transmit.Classes.SelectConversationUI
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findMethodsByExactParameters
7 | import java.lang.reflect.Method
8 |
9 | object Methods {
10 | val SelectConversationUI_checkLimit: Method by wxLazy("SelectConversationUI_checkLimit") {
11 | findMethodsByExactParameters(SelectConversationUI, C.Boolean, C.Boolean).firstOrNull()
12 | }
13 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/wcdb/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.Package.WECHAT_PACKAGE_SQLITE
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 |
8 | /**
9 | * 路径类名正确
10 | */
11 | object Classes {
12 | val SQLiteErrorHandler: Class<*> by wxLazy("SQLiteErrorHandler") {
13 | findClassIfExists("$WECHAT_PACKAGE_SQLITE.DatabaseErrorHandler", wxLoader!!)
14 | }
15 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/wcdb/Package.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb
2 |
3 | /**
4 | * 路径正确
5 | */
6 | object Package {
7 | const val WECHAT_PACKAGE_SQLITE = "com.tencent.wcdb"
8 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/wcdb/database/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.database
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.Package.WECHAT_PACKAGE_SQLITE
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 | /**
8 | * 路径类名正确
9 | */
10 | object Classes {
11 | val SQLiteDatabase: Class<*> by wxLazy("SQLiteDatabase") {
12 | findClassIfExists("$WECHAT_PACKAGE_SQLITE.database.SQLiteDatabase", wxLoader!!)
13 | }
14 | val SQLiteCursorFactory: Class<*> by wxLazy("SQLiteCursorFactory") {
15 | findClassIfExists("$WECHAT_PACKAGE_SQLITE.database.SQLiteDatabase\$CursorFactory", wxLoader!!)
16 | }
17 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/com/tencent/wcdb/support/Classes.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.support
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLazy
4 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal.wxLoader
5 | import com.gh0u1l5.wechatmagician.spellbook.mirror.com.tencent.wcdb.Package.WECHAT_PACKAGE_SQLITE
6 | import com.gh0u1l5.wechatmagician.spellbook.util.ReflectionUtil.findClassIfExists
7 | /**
8 | * 路径类名正确
9 | */
10 | object Classes {
11 | val SQLiteCancellationSignal: Class<*> by wxLazy("SQLiteCancellationSignal") {
12 | findClassIfExists("$WECHAT_PACKAGE_SQLITE.support.CancellationSignal", wxLoader!!)
13 | }
14 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/parser/ApkFile.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.parser
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.util.ParallelUtil.parallelForEach
4 |
5 | import java.io.Closeable
6 | import java.io.File
7 | import java.nio.ByteBuffer
8 | import java.util.zip.ZipEntry
9 | import java.util.zip.ZipFile
10 |
11 | /**
12 | * 封装对 APK 文件的解析操作
13 | *
14 | * 参考了 dongliu 的 apk-parser 项目
15 | *
16 | * Refer: https://github.com/hsiafan/apk-parser
17 | */
18 | @ExperimentalUnsignedTypes
19 | class ApkFile(apkFile: File) : Closeable {
20 | /**
21 | * @suppress
22 | */
23 | private companion object {
24 | private const val DEX_FILE = "classes.dex"
25 | private const val DEX_ADDITIONAL = "classes%d.dex"
26 | }
27 |
28 | constructor(path: String) : this(File(path))
29 |
30 | private val zipFile: ZipFile = ZipFile(apkFile)
31 |
32 | private fun readEntry(entry: ZipEntry): ByteArray =
33 | zipFile.getInputStream(entry).use { it.readBytes() }
34 |
35 | override fun close() =
36 | zipFile.close()
37 |
38 | private fun getDexFilePath(idx: Int) =
39 | if (idx == 1) DEX_FILE else String.format(DEX_ADDITIONAL, idx)
40 |
41 | private fun isDexFileExist(idx: Int): Boolean {
42 | val path = getDexFilePath(idx)
43 | return zipFile.getEntry(path) != null
44 | }
45 |
46 | private fun readDexFile(idx: Int): ByteArray {
47 | val path = getDexFilePath(idx)
48 | return readEntry(zipFile.getEntry(path))
49 | }
50 |
51 | val classTypes: ClassTrie by lazy {
52 | var end = 2
53 | while (isDexFileExist(end)) end++
54 |
55 | val ret = ClassTrie()
56 | (1 until end).parallelForEach { idx ->
57 | val data = readDexFile(idx)
58 | val buffer = ByteBuffer.wrap(data)
59 | val parser = DexParser(buffer)
60 | ret += parser.parseClassTypes()
61 | }
62 | return@lazy ret.apply { mutable = false }
63 | }
64 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/parser/ClassTrie.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.parser
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 | import kotlin.collections.ArrayList
5 |
6 | /**
7 | * 用来储存一个 APK 的 package 结构
8 | *
9 | * 出于性能考虑, 这个类不支持读线程和写线程同时操作, 但支持同类型的线程同时操作
10 | */
11 | class ClassTrie {
12 | /**
13 | * @suppress
14 | */
15 | private companion object {
16 | /**
17 | * 用来将 JVM 格式的类型标识符转换为类名
18 | *
19 | * Example: String 的类型标识符为 "Ljava/lang/String;"
20 | * Refer: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3
21 | */
22 | private fun convertJVMTypeToClassName(type: String) =
23 | type.substring(1, type.length - 1).replace('/', '.')
24 | }
25 |
26 | /**
27 | * 读写开关, 用于增强线程间的安全性
28 | *
29 | * 只有开关设为 true 的时候, 写操作才会被执行
30 | * 只有开关设为 false 的时候, 读操作才会返回有效数据
31 | */
32 | @Volatile var mutable = true
33 |
34 | /**
35 | * package 结构的根结点
36 | */
37 | private val root: TrieNode = TrieNode()
38 |
39 | /**
40 | * 插入一个单独的 JVM 格式的类型标识符
41 | */
42 | operator fun plusAssign(type: String) {
43 | if (mutable) {
44 | root.add(convertJVMTypeToClassName(type))
45 | }
46 | }
47 |
48 | /**
49 | * 插入一组 JVM 格式的类型标识符
50 | */
51 | operator fun plusAssign(types: Array) {
52 | types.forEach { this += it }
53 | }
54 |
55 | /**
56 | * 查找指定包里指定深度的所有类
57 | *
58 | * 出于性能方面的考虑, 只有深度相等的类才会被返回, 比如搜索深度为0的时候, 就只返回这个包自己拥有的类, 不包括它
59 | * 里面其他包拥有的类.
60 | */
61 | fun search(packageName: String, depth: Int): List {
62 | if (mutable) return emptyList()
63 | return root.search(packageName, depth)
64 | }
65 |
66 | /**
67 | * 私有的节点结构
68 | */
69 | private class TrieNode {
70 | val classes: MutableList = ArrayList(50)
71 |
72 | val children: MutableMap = ConcurrentHashMap()
73 |
74 | fun add(className: String) {
75 | add(className, 0)
76 | }
77 |
78 | private fun add(className: String, pos: Int) {
79 | val delimiterAt = className.indexOf('.', pos)
80 | if (delimiterAt == -1) {
81 | synchronized(this) {
82 | classes.add(className)
83 | }
84 | return
85 | }
86 | val pkg = className.substring(pos, delimiterAt)
87 | if (pkg !in children) {
88 | children[pkg] = TrieNode()
89 | }
90 | children[pkg]!!.add(className, delimiterAt + 1)
91 | }
92 |
93 | fun get(depth: Int = 0): List {
94 | if (depth == 0) return classes
95 | return children.flatMap { it.value.get(depth - 1) }
96 | }
97 |
98 | fun search(packageName: String, depth: Int): List {
99 | return search(packageName, depth, 0)
100 | }
101 |
102 | private fun search(packageName: String, depth: Int, pos: Int): List {
103 | val delimiterAt = packageName.indexOf('.', pos)
104 | if (delimiterAt == -1) {
105 | val pkg = packageName.substring(pos)
106 | return children[pkg]?.get(depth) ?: emptyList()
107 | }
108 | val pkg = packageName.substring(pos, delimiterAt)
109 | val next = children[pkg] ?: return emptyList()
110 | return next.search(packageName, depth, delimiterAt + 1)
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/parser/DexHeader.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.parser
2 |
3 | /**
4 | * Dex 格式的文件头
5 | *
6 | * Refer: https://source.android.com/devices/tech/dalvik/dex-format
7 | */
8 | @ExperimentalUnsignedTypes
9 | class DexHeader {
10 | var version: Int = 0
11 |
12 | var checksum: UInt = 0u
13 |
14 | var signature: ByteArray = ByteArray(kSHA1DigestLen)
15 |
16 | var fileSize: UInt = 0u
17 |
18 | var headerSize: UInt = 0u
19 |
20 | var endianTag: UInt = 0u
21 |
22 | var linkSize: UInt = 0u
23 |
24 | var linkOff: UInt = 0u
25 |
26 | var mapOff: UInt = 0u
27 |
28 | var stringIdsSize: Int = 0
29 |
30 | var stringIdsOff: UInt = 0u
31 |
32 | var typeIdsSize: Int = 0
33 |
34 | var typeIdsOff: UInt = 0u
35 |
36 | var protoIdsSize: Int = 0
37 |
38 | var protoIdsOff: UInt = 0u
39 |
40 | var fieldIdsSize: Int = 0
41 |
42 | var fieldIdsOff: UInt = 0u
43 |
44 | var methodIdsSize: Int = 0
45 |
46 | var methodIdsOff: UInt = 0u
47 |
48 | var classDefsSize: Int = 0
49 |
50 | var classDefsOff: UInt = 0u
51 |
52 | var dataSize: Int = 0
53 |
54 | var dataOff: UInt = 0u
55 |
56 | /**
57 | * @suppress
58 | */
59 | companion object {
60 | const val kSHA1DigestLen = 20
61 | const val kSHA1DigestOutputLen = kSHA1DigestLen * 2 + 1
62 | }
63 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/BasicUtil.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.util
2 |
3 | import android.util.Log
4 | import kotlin.concurrent.thread
5 |
6 | /**
7 | * 封装了一批很便利的常用操作
8 | */
9 | object BasicUtil {
10 | /**
11 | * 执行回调函数, 无视它抛出的任何异常
12 | */
13 | @JvmStatic inline fun trySilently(func: () -> T?): T? {
14 | return try { func() } catch (t: Throwable) { null }
15 | }
16 |
17 | /**
18 | * 执行回调函数, 将它抛出的异常记录到 Xposed 的日志里
19 | */
20 | @JvmStatic inline fun tryVerbosely(func: () -> T?): T? {
21 | return try { func() } catch (t: Throwable) {
22 | Log.e("Xposed", Log.getStackTraceString(t)); null
23 | }
24 | }
25 |
26 | /**
27 | * 异步执行回调函数, 将它抛出的记录到 Xposed 的日志里
28 | *
29 | * WARN: 别忘了任何 UI 操作都必须使用 runOnUiThread
30 | */
31 | @JvmStatic inline fun tryAsynchronously(crossinline func: () -> Unit): Thread {
32 | return thread(start = true) { func() }.apply {
33 | setUncaughtExceptionHandler { _, t ->
34 | Log.e("Xposed", Log.getStackTraceString(t))
35 | }
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/FileUtil.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.util
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.graphics.Bitmap
6 | import android.net.Uri
7 | import android.os.SystemClock.elapsedRealtime
8 | import java.io.*
9 | import java.lang.System.currentTimeMillis
10 | import java.text.SimpleDateFormat
11 | import java.util.*
12 |
13 | /**
14 | * 封装了一批关于磁盘 I/O 的方法
15 | */
16 | object FileUtil {
17 | /**
18 | * 将一段数据写入磁盘指定位置
19 | *
20 | * 若文件及其所在目录不存在的话, 会尝试建立该文件和目录
21 | */
22 | @JvmStatic fun writeBytesToDisk(path: String, content: ByteArray) {
23 | val file = File(path).also { it.parentFile.mkdirs() }
24 | val fout = FileOutputStream(file)
25 | BufferedOutputStream(fout).use { it.write(content) }
26 | }
27 |
28 | /**
29 | * 从磁盘上读取一个文件的全部数据
30 | */
31 | @JvmStatic fun readBytesFromDisk(path: String): ByteArray {
32 | val fin = FileInputStream(path)
33 | return BufferedInputStream(fin).use { it.readBytes() }
34 | }
35 |
36 | /**
37 | * 将一个 [Serializable] 对象写入磁盘指定位置
38 | */
39 | @JvmStatic fun writeObjectToDisk(path: String, obj: Serializable) {
40 | val out = ByteArrayOutputStream()
41 | ObjectOutputStream(out).use {
42 | it.writeObject(obj)
43 | }
44 | writeBytesToDisk(path, out.toByteArray())
45 | }
46 |
47 | /**
48 | * 从磁盘上读取一个 [Serializable] 对象
49 | */
50 | @JvmStatic fun readObjectFromDisk(path: String): Any? {
51 | val bytes = readBytesFromDisk(path)
52 | val ins = ByteArrayInputStream(bytes)
53 | return ObjectInputStream(ins).use {
54 | it.readObject()
55 | }
56 | }
57 |
58 | /**
59 | * 将一个 [InputStream] 的内容写入磁盘指定位置
60 | *
61 | * 该函数会同步进行读写, 比较节约内存, 在内存空间不足的设备上非常有帮助
62 | *
63 | * @param path 数据保存路径
64 | * @param ins 提供数据的 [InputStream] 对象
65 | * @param bufferSize 缓冲区大小, 默认值为8192, 设置更大的值可以换来线性的性能提升
66 | */
67 | @JvmStatic fun writeInputStreamToDisk(path: String, ins: InputStream, bufferSize: Int = 8192) {
68 | val file = File(path)
69 | file.parentFile.mkdirs()
70 | val fout = FileOutputStream(file)
71 | BufferedOutputStream(fout).use {
72 | val buffer = ByteArray(bufferSize)
73 | var length = ins.read(buffer)
74 | while (length != -1) {
75 | it.write(buffer, 0, length)
76 | length = ins.read(buffer)
77 | }
78 | }
79 | }
80 |
81 | /**
82 | * 将一张 [Bitmap] 写入磁盘指定位置
83 | */
84 | @JvmStatic fun writeBitmapToDisk(path: String, bitmap: Bitmap) {
85 | val out = ByteArrayOutputStream()
86 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
87 | writeBytesToDisk(path, out.toByteArray())
88 | }
89 |
90 | /**
91 | * 如果指定文件开机后还没被修改过, 那么执行一次写操作
92 | *
93 | * @param writeCallback 实际进行写操作的回调函数
94 | */
95 | @JvmStatic inline fun writeOnce(path: String, writeCallback: (String) -> Unit) {
96 | val file = File(path)
97 | if (!file.exists()) {
98 | writeCallback(path)
99 | return
100 | }
101 | val bootAt = currentTimeMillis() - elapsedRealtime()
102 | val modifiedAt = file.lastModified()
103 | if (modifiedAt < bootAt) {
104 | writeCallback(path)
105 | }
106 | }
107 |
108 | /**
109 | * 基于当前时间创建一个时间戳
110 | */
111 | @JvmStatic fun createTimeTag(): String {
112 | val formatter = SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.getDefault())
113 | return formatter.format(Calendar.getInstance().time)
114 | }
115 |
116 | /**
117 | * 广播告知所有应用: 磁盘上添加了新的图片
118 | */
119 | @JvmStatic fun notifyNewMediaFile(path: String, context: Context?) {
120 | val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
121 | context?.sendBroadcast(intent.apply {
122 | data = Uri.fromFile(File(path))
123 | })
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/MirrorUtil.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.util
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal
4 |
5 | /**
6 | * 封装了一批用于检查“自动适配表达式”的函数
7 | */
8 | object MirrorUtil {
9 | /**
10 | * 返回一个 Object 所声明的所有成员变量(不含基类成员)
11 | */
12 | @JvmStatic fun collectFields(instance: Any): List> {
13 | return instance::class.java.declaredFields.filter { field ->
14 | field.name != "INSTANCE" && field.name != "\$\$delegatedProperties"
15 | }.map { field ->
16 | field.isAccessible = true
17 | val key = field.name.removeSuffix("\$delegate")
18 | val value = field.get(instance)
19 | key to value
20 | }
21 | }
22 |
23 | /**
24 | * 生成一份适配报告, 记录每个自动适配表达式最终指向了微信中的什么位置
25 | */
26 | @JvmStatic fun generateReport(instances: List): List> {
27 | return instances.map { instance ->
28 | collectFields(instance).map {
29 | "${instance::class.java.canonicalName}.${it.first}" to it.second.toString()
30 | }
31 | }.flatten().sortedBy { it.first }
32 | }
33 |
34 | /**
35 | * 将一个用于单元测试的惰性求值对象还原到未求值的状态
36 | *
37 | * WARN: 仅供单元测试使用
38 | */
39 | @JvmStatic fun clearUnitTestLazyFields(instance: Any) {
40 | instance::class.java.declaredFields.forEach { field ->
41 | if (Lazy::class.java.isAssignableFrom(field.type)) {
42 | field.isAccessible = true
43 | val lazyObject = field.get(instance)
44 | if (lazyObject is WechatGlobal.UnitTestLazyImpl<*>) {
45 | lazyObject.refresh()
46 | }
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * 生成一份适配报告, 记录每个自动适配表达式最终指向了微信中的什么位置
53 | *
54 | * 如果某个自动适配表达式还没有进行求值的话, 该函数会强制进行一次求值
55 | *
56 | * WARN: 仅供单元测试使用
57 | */
58 | @JvmStatic fun generateReportWithForceEval(instances: List): List> {
59 | return instances.map { instance ->
60 | collectFields(instance).map {
61 | val value = it.second
62 | if (value is Lazy<*>) {
63 | if (!value.isInitialized()) {
64 | value.value
65 | }
66 | }
67 | "${instance::class.java.canonicalName}.${it.first}" to it.second.toString()
68 | }
69 | }.flatten() // 为了 Benchmark 的准确性, 不对结果进行排序
70 | }
71 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/ParallelUtil.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.util
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.tryVerbosely
4 | import java.util.concurrent.ExecutorService
5 | import java.util.concurrent.Executors
6 | import java.util.concurrent.TimeUnit
7 | import kotlin.concurrent.thread
8 |
9 | /**
10 | * 封装了一批用于并行计算的函数
11 | */
12 | object ParallelUtil {
13 | /**
14 | * 当前设备可用 CPU 数量
15 | */
16 | val processors: Int = Runtime.getRuntime().availableProcessors()
17 |
18 | /**
19 | * 创建一个线程池, 其线程总数固定不变
20 | *
21 | * @param nThread 该线程池的线程总数, 默认值为当前设备的 CPU 总数
22 | */
23 | @JvmStatic fun createThreadPool(nThread: Int = processors): ExecutorService =
24 | Executors.newFixedThreadPool(nThread)
25 |
26 | /**
27 | * 进行一次并行计算的 Map 操作
28 | */
29 | @JvmStatic inline fun List.parallelMap(crossinline transform: (T) -> R): List {
30 | val sectionSize = size / processors
31 |
32 | val main = List(processors) { mutableListOf() }
33 | (0 until processors).map { section ->
34 | thread(start = true) {
35 | for (offset in 0 until sectionSize) {
36 | val idx = section * sectionSize + offset
37 | main[section].add(transform(this[idx]))
38 | }
39 | }
40 | }.forEach { it.join() }
41 |
42 | val rest = (0 until size % processors).map { offset ->
43 | val idx = processors * sectionSize + offset
44 | transform(this[idx])
45 | }
46 |
47 | return main.flatten() + rest
48 | }
49 |
50 | /**
51 | * 进行一次并行计算的 ForEach 操作
52 | */
53 | @JvmStatic inline fun Iterable.parallelForEach(crossinline action: (T) -> Unit) {
54 | val pool = createThreadPool()
55 | val iterator = iterator()
56 | while (iterator.hasNext()) {
57 | val item = iterator.next()
58 | pool.execute {
59 | tryVerbosely { action(item) } // 避免进程崩溃
60 | }
61 | }
62 | pool.shutdown()
63 | pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
64 | }
65 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/main/kotlin/com/gh0u1l5/wechatmagician/spellbook/util/XposedUtil.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.util
2 |
3 | import android.os.Build
4 | import android.os.Handler
5 | import android.os.HandlerThread
6 | import com.gh0u1l5.wechatmagician.spellbook.base.Hooker
7 | import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.trySilently
8 | import com.gh0u1l5.wechatmagician.spellbook.util.BasicUtil.tryVerbosely
9 |
10 | /**
11 | * 封装了一批用于和 Xposed 框架通信的方法
12 | */
13 | object XposedUtil {
14 | /**
15 | * 用于处理 Hook 任务的线程池
16 | */
17 | private val workerPool = ParallelUtil.createThreadPool()
18 |
19 | /**
20 | * 用于分发 Hook 任务的管理线程
21 | */
22 | private val managerThread = HandlerThread("HookHandler").apply { start() }
23 |
24 | /**
25 | * 用于分发 Hook 任务的 [Handler]
26 | */
27 | private val managerHandler: Handler = Handler(managerThread.looper)
28 |
29 | /**
30 | * 依据当前系统的版本选择合适的 Hook 策略
31 | *
32 | * @param hook 用于向 Xposed 框架注册事件的回调函数
33 | */
34 | @JvmStatic private inline fun tryHook(crossinline hook: () -> Unit) {
35 | when {
36 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
37 | /**
38 | * WARN: 对于 Android 7.x 及以上, Xposed 多线程 HOOK 会导致崩溃.
39 | */
40 | tryVerbosely(hook)
41 | }
42 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
43 | workerPool.execute { tryVerbosely(hook) }
44 | }
45 | else -> {
46 | /**
47 | * WARN: 对于 Android 4.x 及以下, MultiDex 的支持还不完善, 日志中会出现大量误报.
48 | */
49 | workerPool.execute { trySilently(hook) }
50 | }
51 | }
52 | }
53 |
54 | /**
55 | * 将 [Hooker] 对象发送给管理线程, 等待进一步的处理
56 | */
57 | @JvmStatic fun postHooker(hooker: Hooker) {
58 | managerHandler.post {
59 | tryHook { hooker.hook() }
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/test/kotlin/com/gh0u1l5/wechatmagician/spellbook/base/VersionUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.base
2 |
3 | import org.junit.Test as Test
4 | import org.junit.Assert.*
5 |
6 | class VersionUnitTest {
7 | @Test fun testVersionCompare() {
8 | assertTrue(Version("1") < Version("2"))
9 | assertTrue(Version("1") == Version("1"))
10 | assertTrue(Version("1.2") < Version("1.2.1"))
11 | assertTrue(Version("1.12") > Version("1.1"))
12 |
13 | assertTrue(Version("6.5.8") > Version("6.5.4"))
14 | assertTrue(Version("6.5.8") >= Version("6.5.4"))
15 | assertTrue(Version("6.5.4") >= Version("6.5.4"))
16 | assertTrue(Version("6.5.4") == Version("6.5.4"))
17 | }
18 | }
--------------------------------------------------------------------------------
/Wechat8Spellbook/src/test/kotlin/com/gh0u1l5/wechatmagician/spellbook/mirror/ReflectionUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.gh0u1l5.wechatmagician.spellbook.mirror
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.WechatGlobal
4 | import com.gh0u1l5.wechatmagician.spellbook.util.MirrorUtil.clearUnitTestLazyFields
5 | import com.gh0u1l5.wechatmagician.spellbook.util.MirrorUtil.collectFields
6 | import com.gh0u1l5.wechatmagician.spellbook.util.MirrorUtil.generateReport
7 | import com.gh0u1l5.wechatmagician.spellbook.util.MirrorUtil.generateReportWithForceEval
8 | import org.junit.Assert.assertEquals
9 | import org.junit.Test
10 |
11 | class ReflectionUnitTest {
12 | companion object {
13 | const val TestObject1Name =
14 | "com.gh0u1l5.wechatmagician.spellbook.mirror.ReflectionUnitTest.TestObject1"
15 | const val TestObject2Name =
16 | "com.gh0u1l5.wechatmagician.spellbook.mirror.ReflectionUnitTest.TestObject2"
17 | }
18 |
19 | object TestObject1 {
20 | val var1 = 1
21 | val var2 = 2
22 | val var3 = 3
23 | }
24 |
25 | object TestObject2 {
26 | val var1 by lazy { 1 }
27 | val var2 by lazy { 2 }
28 | }
29 |
30 | object TestObject3 {
31 | val var1 by WechatGlobal.UnitTestLazyImpl{ 1 }
32 | val var2 by WechatGlobal.UnitTestLazyImpl{ 2 }
33 | }
34 |
35 | @Test fun testCollectFields() {
36 | val fields = collectFields(TestObject1)
37 | assertEquals(3, fields.size)
38 | fields.forEach {
39 | when (it.first) {
40 | "var1" -> assertEquals(1, it.second)
41 | "var2" -> assertEquals(2, it.second)
42 | "var3" -> assertEquals(3, it.second)
43 | else -> throw Exception("Unknown key: ${it.first}")
44 | }
45 | }
46 | }
47 |
48 | @Test fun testGenerateReport() {
49 | val report1 = generateReport(listOf(TestObject1, TestObject2))
50 | assertEquals(5, report1.size)
51 | report1.forEach {
52 | when (it.first) {
53 | "$TestObject1Name.var1" -> assertEquals("1", it.second)
54 | "$TestObject1Name.var2" -> assertEquals("2", it.second)
55 | "$TestObject1Name.var3" -> assertEquals("3", it.second)
56 | "$TestObject2Name.var1" -> assertEquals(lazy { 1 }.toString(), it.second)
57 | "$TestObject2Name.var2" -> assertEquals(lazy { 2 }.toString(), it.second)
58 | else -> throw Exception("Unknown key: ${it.first}")
59 | }
60 | }
61 |
62 | val report2 = generateReportWithForceEval(listOf(TestObject1, TestObject2))
63 | assertEquals(5, report2.size)
64 | report2.forEach {
65 | when (it.first) {
66 | "$TestObject1Name.var1" -> assertEquals("1", it.second)
67 | "$TestObject1Name.var2" -> assertEquals("2", it.second)
68 | "$TestObject1Name.var3" -> assertEquals("3", it.second)
69 | "$TestObject2Name.var1" -> assertEquals("1", it.second)
70 | "$TestObject2Name.var2" -> assertEquals("2", it.second)
71 | else -> throw Exception("Unknown key: ${it.first}")
72 | }
73 | }
74 | }
75 |
76 | @Test fun testClearUnitTestLazyFields() {
77 | assertEquals(1, TestObject3.var1)
78 | assertEquals(2, TestObject3.var2)
79 |
80 | clearUnitTestLazyFields(TestObject3)
81 |
82 | val fields = collectFields(TestObject3)
83 | assertEquals(2, fields.size)
84 | fields.forEach {
85 | when (it.first) {
86 | "var1" -> assertEquals(lazy { 1 }.toString(), it.second.toString())
87 | "var2" -> assertEquals(lazy { 2 }.toString(), it.second.toString())
88 | else -> throw Exception("Unknown key: ${it.first}")
89 | }
90 | }
91 |
92 | assertEquals(1, TestObject3.var1)
93 | assertEquals(2, TestObject3.var2)
94 | }
95 | }
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | buildToolsVersion '28.0.3'
6 |
7 |
8 | defaultConfig {
9 | applicationId "com.huruwo.hposed"
10 | minSdkVersion 21
11 | targetSdkVersion 28
12 | versionCode 10
13 | versionName "12.7"
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | packagingOptions {
18 | exclude 'META-INF/*'
19 | }
20 |
21 |
22 | buildTypes {
23 | // release {
24 | // minifyEnabled false
25 | // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | // }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_1_8
30 | targetCompatibility JavaVersion.VERSION_1_8
31 | }
32 |
33 | android.applicationVariants.all { variant ->
34 | variant.outputs.all {
35 | outputFileName = "KT-V${variant.versionName}.apk"
36 | }
37 | }
38 |
39 | }
40 |
41 | dependencies {
42 | implementation fileTree(include: ['*.jar'], dir: 'libs')
43 | implementation 'com.android.support:appcompat-v7:28.0.0'
44 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
45 | implementation project(path: ':Wechat8Spellbook')
46 | testImplementation 'junit:junit:4.12'
47 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
48 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
49 | compileOnly 'de.robv.android.xposed:api:82'
50 | implementation 'com.blankj:utilcode:1.20.0'
51 | implementation 'com.google.code.gson:gson:2.8.2'
52 | implementation 'com.squareup.okhttp3:okhttp:3.10.0'
53 | implementation group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.5.0'
54 | api 'com.github.tbruyelle:rxpermissions:0.10.2'
55 | api 'io.reactivex.rxjava2:rxandroid:2.0.1'
56 | api 'io.reactivex.rxjava2:rxjava:2.1.3'
57 | api 'com.squareup.retrofit2:converter-gson:2.3.0'
58 | api 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
59 | api 'com.squareup.okhttp3:logging-interceptor:3.12.0'
60 | implementation('com.yanzhenjie:permission:2.0.0-rc12') {
61 | exclude group: 'com.android.support'
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/huruwo/app_xposed/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.huruwo.app_xposed;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.huruwo.app_xposed.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/assets/xposed_init:
--------------------------------------------------------------------------------
1 | com.huruwo.hposed.HookLoader
2 |
--------------------------------------------------------------------------------
/app/src/main/java/com/huruwo/hposed/HookLoader.java:
--------------------------------------------------------------------------------
1 | package com.huruwo.hposed;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.pm.ApplicationInfo;
6 | import android.content.pm.PackageManager;
7 |
8 | import com.blankj.utilcode.util.AppUtils;
9 | import com.huruwo.hposed.utils.LogXUtils;
10 |
11 | import java.io.File;
12 |
13 | import dalvik.system.PathClassLoader;
14 | import de.robv.android.xposed.IXposedHookLoadPackage;
15 | import de.robv.android.xposed.IXposedHookZygoteInit;
16 | import de.robv.android.xposed.XC_MethodHook;
17 | import de.robv.android.xposed.XposedHelpers;
18 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
19 |
20 | import static com.huruwo.hposed.MainHookLoader.MAIN_APP;
21 |
22 | /**
23 | * @author DX
24 | * 这种方案建议只在开发调试的时候使用,因为这将损耗一些性能(需要额外加载apk文件),调试没问题后,直接修改xposed_init文件为正确的类即可
25 | * 可以实现免重启,由于存在缓存,需要杀死宿主程序以后才能生效
26 | * Created by DX on 2017/10/4.
27 | * Modified by chengxuncc on 2019/4/16.
28 | */
29 |
30 | public class HookLoader implements IXposedHookLoadPackage, IXposedHookZygoteInit {
31 | //按照实际使用情况修改下面几项的值
32 | /**
33 | * 当前Xposed模块的包名,方便寻找apk文件
34 | */
35 | private final static String modulePackageName = MAIN_APP;
36 |
37 | /**
38 | * 实际hook逻辑处理类
39 | */
40 | private final String handleHookClass = MainHookLoader.class.getName();
41 | /**
42 | * 实际hook逻辑处理类的入口方法
43 | */
44 | private final String handleHookMethod = "handleLoadPackage";
45 |
46 | private final String initMethod = "initZygote";
47 |
48 | private StartupParam startupparam;
49 |
50 | /**
51 | * 重定向handleLoadPackage函数前会执行initZygote
52 | *
53 | * @param loadPackageParam
54 | * @throws Throwable
55 | */
56 | @Override
57 | public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
58 |
59 | // 排除系统应用
60 | if (loadPackageParam.appInfo == null ||
61 | (loadPackageParam.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) == 1) {
62 | return;
63 | }
64 | //将loadPackageParam的classloader替换为宿主程序Application的classloader,解决宿主程序存在多个.dex文件时,有时候ClassNotFound的问题
65 | XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() {
66 | @Override
67 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
68 | Context context = (Context) param.args[0];
69 | loadPackageParam.classLoader = context.getClassLoader();
70 | Class> cls = getApkClass(context, modulePackageName, handleHookClass);
71 | Object instance = cls.newInstance();
72 | try {
73 | cls.getDeclaredMethod(initMethod, startupparam.getClass()).invoke(instance, startupparam);
74 | }catch (NoSuchMethodException e){
75 | // 找不到initZygote方法
76 | }
77 | try {
78 | cls.getDeclaredMethod(handleHookMethod, loadPackageParam.getClass()).invoke(instance, loadPackageParam);
79 | }catch (NoSuchMethodException e){
80 | // 找不到initZygote方法
81 | }
82 | }
83 | });
84 | }
85 |
86 | /**
87 | * 实现initZygote,保存启动参数。
88 | *
89 | * @param startupParam
90 | */
91 | @Override
92 | public void initZygote(StartupParam startupParam) {
93 | this.startupparam = startupParam;
94 | }
95 |
96 | private Class> getApkClass(Context context, String modulePackageName, String handleHookClass) throws Throwable {
97 | File apkFile = findApkFile(context, modulePackageName);
98 | if (apkFile == null) {
99 | throw new RuntimeException("寻找模块apk失败");
100 | }
101 | //加载指定的hook逻辑处理类,并调用它的handleHook方法
102 | PathClassLoader pathClassLoader = new PathClassLoader(apkFile.getAbsolutePath(), ClassLoader.getSystemClassLoader());
103 | Class> cls = Class.forName(handleHookClass, true, pathClassLoader);
104 | return cls;
105 | }
106 |
107 | /**
108 | * 根据包名构建目标Context,并调用getPackageCodePath()来定位apk
109 | *
110 | * @param context context参数
111 | * @param modulePackageName 当前模块包名
112 | * @return apk file
113 | */
114 | private File findApkFile(Context context, String modulePackageName) {
115 | if (context == null) {
116 | return null;
117 | }
118 | try {
119 | Context moudleContext = context.createPackageContext(modulePackageName, Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
120 | String apkPath = moudleContext.getPackageCodePath();
121 | return new File(apkPath);
122 | } catch (PackageManager.NameNotFoundException e) {
123 | e.printStackTrace();
124 | }
125 | return null;
126 | }
127 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/huruwo/hposed/MainHookLoader.java:
--------------------------------------------------------------------------------
1 | package com.huruwo.hposed;
2 |
3 | import com.gh0u1l5.wechatmagician.spellbook.SpellBook;
4 | import com.huruwo.hposed.action.Alert;
5 | import com.huruwo.hposed.action.Message;
6 | import com.huruwo.hposed.utils.LogXUtils;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import de.robv.android.xposed.IXposedHookLoadPackage;
12 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
13 |
14 |
15 | public class MainHookLoader implements IXposedHookLoadPackage {
16 |
17 | public static String MAIN_APP = "com.huruwo.hposed";
18 |
19 | @Override
20 | public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
21 | LogXUtils.e(loadPackageParam.packageName);
22 | if(SpellBook.INSTANCE.isImportantWechatProcess(loadPackageParam)){
23 | LogXUtils.e("hook wechat" +loadPackageParam.packageName);
24 | List