前提
为了更好的维持我的博客系统,想着之后的博客都发布于github.io, 那么如果你有搭建过github.io 那么相比都知道YAML, 通过MARK down 中插入YAML FORMAT 语言,我们可以控制博客的标题、评论等等。。那么简单来说就是我不想用Typora每次建立一个新blog,都需要繁琐的插入那一堆YAML,比如
title: position、anchorPoint、frame理解
tags: iOS
key: 107
# article_header:
# type: cover
# image:
# src: https://user-images.githubusercontent.com/8369671/80915045-153ff780-8d82-11ea-9acf-6ccbf2b05d9d.png
OK,所以首先呢我把Typora的所有文档看了一遍,没有入口可配置。那咋办?
想法也就从这诞生了,我能不能写个插件,在Typora 新建文件的时候,手动插入预置文字,接下来记录插件开发过程,成功不成功最后见分晓!
着手干(查资料)
通过查看资料,我们需要两个东西辅助,1.class-dump) 2. insert_dylib
基本原理就是:
Mach-O 二进制文件Load Commands中的 LC_LOAD_DYLIB 标头告诉 macOS在执行期间要加载哪些动态库 (dylib)。所以我们只需要在二进制文件中添加一条LC_LOAD_DYLIB就可以。而insert_dylib工具已经为我们实现了添加的功能
接下来一个一个解释:
-
class-dump
通过名字我们大概能猜到这是一个什么工具,类似一个解释类的工具,确实他也是干这个的,正确安装之后,我们可以通过class-dump [option] Mach-o文件,输出mac app的暴露类的相关属性,OK,我们用的就是class-dump -H Mach-o文件 输出头文件,我们通过头文件查看mac app源头文件,然后找到需要hook的类进行hook(具体的安装可以点击超链接查看)
-
insert_dylib
这个工具,可以帮助我们插入一个dylib 到Mach-o二进制文件中,所以也就是说我们需要做一个framework然后通过这个库,嵌入到app的Mach-o二进制文件里
ok,工具都全乎了,接下来的时间,就是需要我们做一个framework,这个framework是用专门hook的动态库。
当然hook,Objective-C里就是runtime里的Swizzing Method搞定,这个iOS 开发应该都用过不少(常规技术)
-
具体实践
-
new project - framework
我们新建一个framework,用来做动态库
-
查看class-dump的类,hook 找到的类做功能
cd 到 /Applications/Typora.app/Contents/MacOS/
使用class-dump -H /Applications/Typora.app/Contents/MacOS/Typora -o /Users/haoyh02/Desktop/typora.h 输出头文件解析到桌面目标目录,如下
ok,接下来我就需要分析头文件,找出自己需要hook的类,以及方法,当然这个过程才是最漫长的,而且是不断的尝试出来的。具体的分析我就不赘述了,就是看代码呗。
最终我们找到了LibraryCommands 类,他大概就是一些文件命令处理,比如新建文件,当然我们hook的是这个类,但是具体文件处理则是Document这个类,集成于NSDocument。
具体看代码
// // TyporaAutoRejectHook.m // TyporaAutoReject // // Created by 郝玉鸿 on 2022/8/26. // #import <Foundation/Foundation.h> #import <AppKit/AppKit.h> #import "TyporaAutoReject.h" #import <objc/runtime.h> void ty_hook(Class originClass, SEL originSelector, Class swizzClass, SEL swizzSelector) { Method originalMethod = class_getInstanceMethod(originClass, originSelector); Method swizzledMethod = class_getInstanceMethod(swizzClass, swizzSelector); if(originalMethod && swizzledMethod) { method_exchangeImplementations(originalMethod, swizzledMethod); } } @interface Document : NSDocument @end @interface LibraryCommands: NSObject @property(retain) Document *document; @end @implementation NSObject (Typora) + (void)hookThunder{ ty_hook(objc_getClass("LibraryCommands"), @selector(createFile:), [self class], @selector(hook_newDocument:)); } - (void)hook_newDocument:(id)args { [self hook_newDocument:args]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ LibraryCommands *commans = (LibraryCommands *)self; Document *document = commans.document; NSString *injectString = @"---\ntitle: <xxxx> \ntags: [iOS] [MAC] [Plugin]> \nkey: xxx \n# article_header:\n# type: cover \n# image:\n # src: https://user-images.githubusercontent.com/8369671/80915045-153ff780-8d82-11ea-9acf-6ccbf2b05d9d.png \n---"; [document setValue:injectString forKeyPath:@"content"]; [document writeToURL:document.fileURL ofType:document.fileType error:nil]; [document performSelector:NSSelectorFromString(@"syncToClient")]; [document performSelector:NSSelectorFromString(@"syncToSelf")]; }); } @end static void __attribute__((constructor)) initialize(void) { [NSObject hookThunder]; }
很简单,思路就是hook到新建文件的方法,然后2s之后,文件内插入一段预置文字。然后同步界面。
-
copy framework的产出(编译,products/xxx.framework)到mac app 下的contents/MacOS
OK,接下来我们需要build工程,得到products/xxx.framework 产物。xcode13 隐藏了products,我们只需要打开pbxproj文件, 修改mainGroup 和 productRefGroup 一样(本身也是一样的,我们只需要copy再次保存就好了),保存就可以出现,也可以到DerivedData里去找
然后copy framework,到/Applications/Typora.app/Contents/ 下即可。
-
执行insert_dylib 命令
./insert_dylib --all-yes /Applications/Typora.app/Contents/MacOS/TyporaAutoReject.framework/TyporaAutoReject Typora_backup Typora
执行这个命令后,即可嵌入framework
-
重新打开应用
一定要重新打开应用,否则不生效(这个其实不用想也是这样,毕竟我们是编译型程序)
-
ok,当然这个步骤很麻烦,为了我们能更好的重复验证,我写了脚本执行,这些命令
sudo rm -d -r /Applications/Typora.app/Contents/MacOS/TyporaAutoReject.framework sudo mv -f /Users/haoyh02/Library/Developer/Xcode/DerivedData/TyporaAutoReject-duhvxgpyrtuykugkepbmpmhciiyh/Build/Products/Debug/* /Applications/Typora.app/Contents/MacOS/ ./insert_dylib --all-yes /Applications/Typora.app/Contents/MacOS/TyporaAutoReject.framework/TyporaAutoReject Typora_backup Typora
-
至此,我们为Typora做的小插件,完全生效了。我们来看一下效果: