美文网首页
微信自动抢红包(一):界面

微信自动抢红包(一):界面

作者: HotPotCat | 来源:发表于2021-05-22 15:04 被阅读0次

一、界面分析

要在微信中加入功能,首先要分析界面找到切入点。假如我们要在微信的设置界面加入自动抢红包的配置。
进入设置界面分析页面。可以通过cycript或者view debug分析(reveal也可以)。

image.png
定位到控制器是NewSettingViewController<0x12648ee00>,这个控制器视图是典型的tableView

view debug挂载的时候有可能失败,杀掉重来就可以了。

cycript也可以定位到

cy# HPCurrentVC()
#"<NewSettingViewController: 0x12648ee00>"

我们现在需要定位到数据源,那么要先定位到TableView

cy# pviews()
image.png
这样就找到了WCTableView <0x12651d000>

获取datasource发现数据源是WCTableViewManager< 0x281aeffc0 >

cy# #0x12651d000.dataSource
#"<WCTableViewManager: 0x281aeffc0>"

这个时候我们就找到了NewSettingViewController<0x12648ee00>以及数据源WCTableViewManager< 0x281aeffc0 >
现在需要做的就是找到数据源和视图控制器的对应关系。

看下数据源WCTableViewManager< 0x281aeffc0 >都有什么东西。

//tableView
cy# #0x281aeffc0.tableView
#"<WCTableView: 0x12651d000; baseClass = UITableView; frame = (0 0; 375 667); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x281aec2a0>; layer = <CALayer: 0x2816f0680>; contentOffset: {0, -64}; contentSize: {375, 621}; adjustedContentInset: {64, 0, 0, 0}; dataSource: <WCTableViewManager: 0x281aeffc0>>"
//sections
cy# #0x281aeffc0.sections
@[#"<WCTableViewSectionManager: 0x283c8a5a0>",#"<WCTableViewSectionManager: 0x283c8a7d0>",#"<WCTableViewSectionManager: 0x283c8a990>",#"<WCTableViewSectionManager: 0x283c8aae0>",#"<WCTableViewSectionManager: 0x283c8abc0>",#"<WCTableViewSectionManager: 0x283c8aca0>"]

这样就验证了数据源中有tableViewsections。那么这里section正好6个和界面对应,修改tableView的背景色:

cy# #0x12651d000.backgroundColor = [UIColor redColor]
#"UIExtendedSRGBColorSpace 1 0 0 1"

验证了这个tableView就是当前页面的tableView

接着导出头文件:

class-dump -H WeChat -o ./Headers

在头文件中查找WCTableViewManager

@interface WCTableViewManager : NSObject <UITableViewDelegate, UITableViewDataSource, tableViewDelegate>
{
    MMTableView *_tableView;
    NSMutableArray *_sections;
}
- (void)tableView:(id)arg1 didSelectRowAtIndexPath:(id)arg2;
- (double)tableView:(id)arg1 heightForRowAtIndexPath:(id)arg2;
- (id)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2;
- (long long)tableView:(id)arg1 numberOfRowsInSection:(long long)arg2;
- (long long)numberOfSectionsInTableView:(id)arg1;

确认WCTableViewManager持有了tableViewsections。并且提供了tableView相关的代理。

二、界面分析找到注入点

通过上面的分析,我们要在设置页面加一个抢红包设置模块,假如有一个开关,有一个刷新时间。那么我们需要加一个section和两个cell
那么我们需要修改WCTableViewManager的以下方法达到目的:

- (id)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2;
- (long long)tableView:(id)arg1 numberOfRowsInSection:(long long)arg2;
- (long long)numberOfSectionsInTableView:(id)arg1;

这样我们基本的hook框架就搭建好了:

%hook WCTableViewManager

//返回cell
- (id)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2 {
    return %orig;
}

//每一组多少数据
- (long long)tableView:(id)arg1 numberOfRowsInSection:(long long)arg2 {
    return %orig;
}

//返回组
- (long long)numberOfSectionsInTableView:(id)arg1 {
    return %orig;
}

%end

由于已经知道了这三个方法是tableView的代理方法,所以优化下参数:

%hook WCTableViewManager

//返回cell
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    return %orig;
}

//每一组多少数据
- (long long)tableView:(UITableView *)tableView numberOfRowsInSection:(long long)section {
    return %orig;
}

//返回组
- (long long)numberOfSectionsInTableView:(UITableView *)tableView {
    return %orig;
}

%end

三、通过logos修改微信设置界面

首先要修改返回的分组,需要增加一组。

//返回组
- (long long)numberOfSectionsInTableView:(UITableView *)tableView {
    //获取成员变量,通过声明sections或者kvc也可以
    NSMutableArray *arr = MSHookIvar<NSMutableArray*>(self,"_sections");
    NSLog(@"数据个数%ld,orig:%ld",arr.count,%orig);
    return %orig;
}

在调试的过程中发现我的页面和设置页面都用到了WCTableViewManager,通过cycript也可以验证:

cy#  choose(WCTableViewManager)
[#"<WCTableViewManager: 0x28293a610>",#"<WCTableViewManager: 0x282a41ec0>"]

那么由于WCTableViewManager是个通用类,那么我们只能修改NewSettingViewController页面的。那么可以通过响应链条来找到控制器。

cy#  choose(WCTableViewManager)
[#"<WCTableViewManager: 0x28293a610>",#"<WCTableViewManager: 0x282a41ec0>"]
cy# #0x28293a610.tableView.nextResponder.nextResponder
#"<NewSettingViewController: 0x112cec600>"
cy# #0x282a41ec0.tableView.nextResponder.nextResponder.nextResponder
#"<MoreViewController: 0x112c68a00>"

这样就能通过WCTableViewManager找到NewSettingViewController了。

判断是否设置页面
由于其它代理方法中也要有类似逻辑,所以直接封装个方法。为了其它方法中调用能编译通过需要声明:

//为了编译通过
@interface WCTableViewManager

- (BOOL)isNewSettingVC:(UITableView *)tableView;

@end

//由于WCTableViewManager是个通用工具,那么需要定位到设置页面才修改。
%new
- (BOOL)isNewSettingVC:(UITableView *)tableView {
    if ([tableView.nextResponder.nextResponder isKindOfClass: %c(NewSettingViewController)]) { //是设置页面
        return YES;
    }
    return NO;
}

注入后完整代码如下:

#import <UIKit/UIKit.h>

//为了编译通过
@interface WCTableViewManager

- (BOOL)isNewSettingVC:(UITableView *)tableView;

- (long long)numberOfSectionsInTableView:(UITableView *)tableView;

@end

%hook WCTableViewManager

- (double)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    //高度在模型数组中,不设置为0。
    //double height = %orig;
    if ([self isNewSettingVC:tableView] && (indexPath.section == [self numberOfSectionsInTableView:tableView] - 1)) { //设置页面 & 最后一组
        return 56.0;
    }
    return %orig;
}

//返回cell
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([self isNewSettingVC:tableView] && (indexPath.section == [self numberOfSectionsInTableView:tableView] - 1)) { //设置页面 & 最后一组
        if(indexPath.row == 0) {//开关cell
            UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SwitchCell"];
            cell.backgroundColor = [UIColor whiteColor];
            cell.textLabel.text = @"SwitchCell";
            return cell;
        } else if(indexPath.row == 1){//时间cell
            UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"TimeCell"];
            cell.backgroundColor = [UIColor whiteColor];
            cell.textLabel.text = @"TimeCell";
            return cell;
        }
    }
    return %orig;
}

//每一组多少数据
- (long long)tableView:(UITableView *)tableView numberOfRowsInSection:(long long)section {
    if ([self isNewSettingVC:tableView] && (section == [self numberOfSectionsInTableView:tableView] - 1)) { //设置页面 & 最后一组
        return 2;
    }
    return %orig;
}

//返回组
- (long long)numberOfSectionsInTableView:(UITableView *)tableView {
    if ([self isNewSettingVC:tableView]) {
//        //获取成员变量,通过声明sections或者kvc也可以
//        NSMutableArray *arr = MSHookIvar<NSMutableArray*>(self,"_sections");
//        NSLog(@"数据个数%ld,orig:%ld",arr.count,%orig);
        return %orig + 1;//多加1组
    }
    return %orig;
}

//由于WCTableViewManager是个通用工具,那么需要定位到设置页面才修改。
%new
- (BOOL)isNewSettingVC:(UITableView *)tableView {
    if ([tableView.nextResponder.nextResponder isKindOfClass: %c(NewSettingViewController)]) { //是设置页面
        NSLog(@"当前页面是设置页面");
        return YES;
    }
    return NO;
}

%end
注入cell
这个时候需要的两个cell就已经注入了。

四、完善cell

添加cellUI

//返回cell
- (id)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([self isNewSettingVC:tableView] && (indexPath.section == [self numberOfSectionsInTableView:tableView] - 1)) { //设置页面 & 最后一组
        UITableViewCell *cell = nil;
        if(indexPath.row == 0) {//开关cell
            cell = [tableView dequeueReusableCellWithIdentifier:@"SwitchCell"];
            if (!cell) {
                cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"SwitchCell"];
            }
            cell.textLabel.text = @"自动抢红包";
            //开关
            BOOL switchOn = [HPDefaults boolForKey:HPSwithKey];
            UISwitch *switchView = [[UISwitch alloc] init];
            switchView.on = switchOn;
            [switchView addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
            cell.accessoryView = switchView;
            cell.imageView.image = [UIImage imageNamed:switchOn ? @"HP_switch_on" : @"HP_switch_off"];
        } else if(indexPath.row == 1){//时间cell
            cell = [tableView dequeueReusableCellWithIdentifier:@"TimeCell"];
            if (!cell) {
                cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"TimeCell"];
            }
            cell.textLabel.text = @"等待时间(秒)";
            //时间输入框
            UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 150, 40)];
            textField.text = [HPDefaults valueForKey:HPTimeKey];
            textField.borderStyle = UITextBorderStyleRoundedRect;
            textField.keyboardType = UIKeyboardTypeDecimalPad;
            cell.accessoryView = textField;
            cell.imageView.image = [UIImage imageNamed:@"HP_time"];
        }
        cell.backgroundColor = [UIColor whiteColor];
        return cell;
    }
    return %orig;
}

%new
- (void)switchChanged:(UISwitch *)switchView {
    [HPDefaults setBool:switchView.isOn forKey:HPSwithKey];
    [HPDefaults synchronize];
    [MSHookIvar<UITableView*>(self,"_tableView") reloadData];
}
  • 通过NSUserDefaults持久化数据。
  • 通过将资源文件加入到.app中重新打包从而注入资源文件。

资源文件添加
1.可以将Bundle资源和storyboard添加到Resources中:

image.png
2.也可以直接放入到Copy Bundle Resouces
image.png
3.将资源文件加入到.app中重新打包
zip -ry WeChat.ipa Payload/

最终视图如下:


抢红包UI

五、页面优化

设置时间持久化:

//时间cell中
//监听变化,微信目前支持iOS11以上,所以可以不移除。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldDidChangeValue:) name:UITextFieldTextDidChangeNotification object:textField];

%new
- (void)textFieldDidChangeValue:(NSNotification *)notification {
    UITextField *textField = (UITextField *)[notification object];
    [HPDefaults setValue:textField.text forKey:HPTimeKey];
    [HPDefaults synchronize];
}

在输入时间时发现当前页面没有管理键盘的弹出,所以需要增加对键盘的监听处理,那么键盘需要针对页面而言,所以在NewSettingViewController页面中监听键盘,直接在viewDidLoad中添加:

#define HP_SCREEN_WIDTH [UIScreen  mainScreen].bounds.size.width
#define HP_SCREEN_HEIGHT [UIScreen  mainScreen].bounds.size.height

@interface NewSettingViewController : UIViewController

@end

%hook NewSettingViewController

- (void)viewDidLoad {
    %orig;
    //监听键盘
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}

%new
- (void)keyboardWillShow:(NSNotification *)notification {
    UIView *view = self.view;
    //这里是改变view的frame并不是Tableiew的。由viewdebug可以看出来TabelView在整个view上面,在这里挪动view最快捷。
    CGRect keyBoardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    view.frame = CGRectMake(0, -keyBoardRect.size.height, HP_SCREEN_WIDTH, HP_SCREEN_HEIGHT);
}

%new
- (void)keyboardWillHide:(NSNotification *)notification {
    UIView *view = self.view;
    view.frame = CGRectMake(0, 0, HP_SCREEN_WIDTH, HP_SCREEN_HEIGHT);
}

%end

在键盘的操作中,改变的是viewframe并不是TableView的。由viewdebug可以看出来TabelView在整个view上面,在这里挪动view最快捷。

这个时候页面能够正常展示了,但是键盘收起没有触发。分析数据源发现有scrollView相关的代理,那么在 拖拽事件中处理键盘收起:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    %orig;
    if ([self isNewSettingVC:scrollView]) {
        [MSHookIvar<UITableView *>(self,"_tableView") endEditing:YES];
    }
}

至此整个UI逻辑就完成了,当然可以对输入框的输入和剪切板粘贴等做进一步限制处理。
详细代码逻辑见Demo

相关文章

网友评论

      本文标题:微信自动抢红包(一):界面

      本文链接:https://www.haomeiwen.com/subject/piqmjltx.html