前言
作为哔哩哔哩的重度用户,我一直期待官方支持 3 倍速播放,但该功能迟迟未上线。于是,我利用 iOS 逆向工程知识,为 B 站 App 添加这一功能。
修改前 :最高仅支持 2.0 倍速。

修改后 :成功添加 3.0 倍速选项

本系列分为多篇,本文聚焦 横屏视频全屏播放 场景下的 3 倍速实现。
系列回顾:
场景

开发环境
-
哔哩哔哩版本:
8.41.0 -
IDA Professional 9.0 -
Lookin
分析
1. 定位倍速面板控件
- 通过
Lookin查看层级,播放速度浮层为BBPlayerFloatingWidgetView,其包含_rootWidget属性(类型为BBPlayerNavigationWidget)。

objc
@interface BBPlayerFloatingWidgetView : UIView {
/* instance variables */
BBPlayerContext *_context;
BBPlayerWidget *_rootWidget;
}
/* instance methods */
- (id)initWithContext:(id)context rootWidget:(id)widget;
- (id)hitTest:(struct CGPoint { double x0; double x1; })test withEvent:(id)event;
@end
2. 追踪倍速列表
- 我们在
Xcode添加符号断点-[BBPlayerFloatingWidgetView initWithContext:rootWidget:],看看rootWidget的值是什么?

-[BBPlayerFloatingWidgetView initWithContext:rootWidget:]断点触发,我们打印参数的值,知道rootWidget的类型是BBPlayerNavigationWidget
objc
(lldb) p (id)$x3
(BBPlayerNavigationWidget *) 0x00000002827e2be0
- 从
Mach-O文件导出的BBPlayerNavigationWidget的OC头文件可以知道,BBPlayerNavigationWidget继承自BBPlayerObject,BBPlayerObject有subWidgets属性
objc
@interface BBPlayerNavigationWidget : BBPlayerFloatingWidget <CAAnimationDelegate, UIGestureRecognizerDelegate> {
/* instance variables */
NSMutableArray *_widgets;
UIView *_view;
}
@property (readonly, nonatomic) BBPlayerFloatingWidget *rootWidget;
...
/* instance methods */
- (id)initWithContext:(id)context rootWidget:(id)widget;
- (id)view;
- (void)pushWidget:(id)widget animated:(_Bool)animated;
- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id /* block */)completion;
...
@end
objc
@interface BBPlayerWidget : BBPlayerObject <BBPlayerWidgetDelegate>
@property (nonatomic) long long lifecycleFlag;
@property (readonly, nonatomic) UIView *view;
@property (readonly, weak, nonatomic) BBPlayerWidget *superWidget;
@property (readonly, copy, nonatomic) NSArray *subWidgets;
...
- (void)willAppear:(_Bool)appear;
- (void)willLayoutSubWidgets;
- (void)didLayoutSubWidgets;
- (void)didAppear:(_Bool)appear;
- (void)willDisappear:(_Bool)disappear;
- (void)didDisappear:(_Bool)disappear;
...
@end
- 我们在
Xcode尝试添加符号断点-[BBPlayerNavigationWidget didLayoutSubWidgets],但是添加不成功

- 我们
hookBBPlayerNavigationWidget的didLayoutSubWidgets方法,并添加断点
objc
%hook BBPlayerNavigationWidget
- (void)didLayoutSubWidgets {
%orig;
NSLog(@"%@:%@-%p-%s", nj_logPrefix, NSStringFromClass([(id)self class]), self, __FUNCTION__);
}
%end
BBPlayerNavigationWidget的didLayoutSubWidgets方法的断点触发,我们打印subWidgets属性,发现为空
objc
(lldb) po [self subWidgets]
nil
- 我们知道
BBPlayerNavigationWidget有个_widgets成员变量,我们打印它的值,发现为空
objc
@interface BBPlayerNavigationWidget : BBPlayerFloatingWidget <CAAnimationDelegate, UIGestureRecognizerDelegate> {
/* instance variables */
NSMutableArray *_widgets;
UIView *_view;
}
objc
(lldb) po ((BBPlayerNavigationWidget *)self)->_widgets
<__NSArrayM 0x280448600>(
)
- 我们知道
BBPlayerNavigationWidget有个- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion方法,我们添加断点,看widget的值为多少
objc
%hook BBPlayerNavigationWidget
- (void)didLayoutSubWidgets {
%orig;
NSLog(@"%@:%@-%p-%s", nj_logPrefix, NSStringFromClass([(id)self class]), self, __FUNCTION__);
}
- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion {
%orig;
}
- (void)addWidget:(id)widget animated:(_Bool)animated completion:(id)completion {
%orig;
}
%end
- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion方法的断点触发,发现widget的类型是BBPlayerPlaybackRateListWidget
objc
(lldb) p widget
(BBPlayerPlaybackRateListWidget?) 0x0000000282437b60
- 而且
- (void)pushWidget:(id)widget animated:(_Bool)animated completion:(id)completion方法执行完后,_widgets属性有值了,subWidgets属性也有值了,类型是BBPlayerPlaybackRateListWidget
objc
(lldb) po ((BBPlayerNavigationWidget *)self)->_widgets
<__NSArrayM 0x2804a05d0>(
<BBPlayerPlaybackRateListWidget: 0x282437b60>
)
(lldb) po [self subWidgets]
<__NSSingleObjectArrayI 0x2817c38e0>(
<BBPlayerPlaybackRateListWidget: 0x282437b60>
)
- 从
Mach-O文件导出的BBPlayerCommonSwift.BBPlayerPlaybackRateListWidget的swift文件可以知道,它有一个rateArray属性,猜测它是播放速度数组,rateArray属性只读,getter方法叫做sub_10D829128
swift
class BBPlayerCommonSwift.BBPlayerPlaybackRateListWidget: BBPlayerFloatingWidget {
/* fields */
var lazy tableView: UITableView?
var lazy playbackRate: (private) ?
var lazy rateArray: Array -> BBPlayerCommonSwift.BBPlayerPlayerRateModel ?
var lazy playbackRateProxy: BBPlayerPlaybackRateValueService??
/* methods */
func tableView.getter.sub_10d828e50
// <stripped> func tableView.setter
// <stripped> func tableView.modify
func playbackRate.getter.sub_10d829074
// <stripped> func playbackRate.setter
// <stripped> func playbackRate.modify
func rateArray.getter.sub_10d829128
// <stripped> func rateArray.setter
// <stripped> func rateArray.modify
...
}
- 我们添加一个符号断点
sub_10D829128,看看rateArray的值是多少?

sub_10D829128断点触发,打印它的返回值,发现是播放速度,这也证明了rateArray属性是播放速度数组
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStorageC19BBPlayerCommonSwift23BBPlayerPlayerRateModel_$ 0x2835d09b0>(
<BBPlayerPlayerRateModel: 0x28107fb20; value = 2.000000; text = 2.0>,
<BBPlayerPlayerRateModel: 0x28107fae0; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x28107dc40; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x28107fc60; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x28107e880; value = 0.750000; text = 0.75>,
<BBPlayerPlayerRateModel: 0x28107e660; value = 0.500000; text = 0.5>
)
3. 找到数组创建函数
- 从
IDA查看sub_10D829128的伪代码,分析后猜测是由sub_10D82E870方法创建的播放速度数组
objc
__int64 sub_10D829128()
{
...
v1 = OBJC_IVAR___BBPlayerPlaybackRateListWidget____lazy_storage___rateArray;
v2 = *(_QWORD *)(v0 + OBJC_IVAR___BBPlayerPlaybackRateListWidget____lazy_storage___rateArray);
if ( v2 )
{
v3 = *(_QWORD *)(v0 + OBJC_IVAR___BBPlayerPlaybackRateListWidget____lazy_storage___rateArray);
}
else
{
v4 = sub_10D82E870();
v3 = sub_10D82918C(v4);
v5 = *(_QWORD *)(v0 + v1);
*(_QWORD *)(v0 + v1) = v3;
((void (*)(void))swift_bridgeObjectRetain)();
swift_bridgeObjectRelease(v5);
v2 = 0LL;
}
swift_bridgeObjectRetain(v2);
return v3;
}
- 查看
sub_10D82E870方法的伪代码,知道它返回一个包含 6 个BBPlayerPlayerRateModel的数组。
c
__int64 sub_10D82E870()
{
...
sub_10D7DBF44();
v0 = swift_allocObject();
*(_OWORD *)(v0 + 16) = xmmword_110613B30;
v1 = (objc_class *)type metadata accessor for BBPlayerPlayerRateModel();
v2 = (char *)objc_msgSend(objc_allocWithZone(v1), "init");
v3 = &v2[OBJC_IVAR___BBPlayerPlayerRateModel_value];
swift_beginAccess(&v2[OBJC_IVAR___BBPlayerPlayerRateModel_value], v38, 1LL, 0LL);
*(_QWORD *)v3 = 0x3FE0000000000000LL;
v4 = &v2[OBJC_IVAR___BBPlayerPlayerRateModel_text];
swift_beginAccess(&v2[OBJC_IVAR___BBPlayerPlayerRateModel_text], v37, 1LL, 0LL);
v5 = *((_QWORD *)v4 + 1);
*(_QWORD *)v4 = 3485232LL;
*((_QWORD *)v4 + 1) = 0xE300000000000000LL;
swift_bridgeObjectRelease(v5);
*(_QWORD *)(v0 + 32) = v2;
v6 = (char *)objc_msgSend(objc_allocWithZone(v1), "init");
v7 = &v6[OBJC_IVAR___BBPlayerPlayerRateModel_value];
swift_beginAccess(&v6[OBJC_IVAR___BBPlayerPlayerRateModel_value], v36, 1LL, 0LL);
*(_QWORD *)v7 = 0x3FE8000000000000LL;
v8 = &v6[OBJC_IVAR___BBPlayerPlayerRateModel_text];
swift_beginAccess(&v6[OBJC_IVAR___BBPlayerPlayerRateModel_text], v35, 1LL, 0LL);
v9 = *((_QWORD *)v8 + 1);
*(_QWORD *)v8 = 892808752LL;
*((_QWORD *)v8 + 1) = 0xE400000000000000LL;
swift_bridgeObjectRelease(v9);
*(_QWORD *)(v0 + 40) = v6;
v10 = (char *)objc_msgSend(objc_allocWithZone(v1), "init");
v11 = &v10[OBJC_IVAR___BBPlayerPlayerRateModel_value];
swift_beginAccess(&v10[OBJC_IVAR___BBPlayerPlayerRateModel_value], v34, 1LL, 0LL);
*(_QWORD *)v11 = 0x3FF0000000000000LL;
v12 = &v10[OBJC_IVAR___BBPlayerPlayerRateModel_text];
swift_beginAccess(&v10[OBJC_IVAR___BBPlayerPlayerRateModel_text], v33, 1LL, 0LL);
v13 = *((_QWORD *)v12 + 1);
*(_QWORD *)v12 = 3157553LL;
*((_QWORD *)v12 + 1) = 0xE300000000000000LL;
swift_bridgeObjectRelease(v13);
*(_QWORD *)(v0 + 48) = v10;
...
return v0;
}
- 我们添加一个符号断点
sub_10D82E870,看看方法返回值多少?

sub_10D82E870断点触发,打印它的返回值,发现是播放速度数组[0.5、0.75、1.0、1.25、1.5、2.0],这也证明了是由sub_10D82E870方法创建的播放速度数组
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStorageC19BBPlayerCommonSwift23BBPlayerPlayerRateModel_$ 0x2834b2da0>(
<BBPlayerPlayerRateModel: 0x281164280; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x281165760; value = 0.750000; text = 0.75>,
<BBPlayerPlayerRateModel: 0x2811655c0; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x281164840; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x281165500; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x281164a40; value = 2.000000; text = 2.0>
)
越狱解决方案
MSHookFunction
MSHookFunction 来自 Cydia Substrate(MobileSubstrate) ,用于 直接 Hook 一个函数地址,而不是 Objective-C 方法。
函数原型
arduino
void MSHookFunction(
void *symbol,
void *replace,
void **result
);
参数说明
| 参数 | 说明 |
|---|---|
symbol |
原始函数地址 |
replace |
你的替换函数 |
result |
保存原始函数指针(可选) |
方案
- 使用
MSHookFunction直接hook sub_10D82E870,修改返回数组的value和text属性。
核心代码:
objc
#import "NJChangePlaybackRateTool.h"
#import "NJCommonDefine.h"
static const double NJChangePlaybackRateFlag = 3.0;
@interface NJChangePlaybackRateTool ()
/// 播放速度
@property (nonatomic, strong) NSArray<NSString *> *playbackRates;
@end
@implementation NJChangePlaybackRateTool
#pragma mark - Life Cycle Methods
- (instancetype)init {
self = [super init];
if (self) {
[self doInit];
}
return self;
}
#pragma mark - Do Init
- (void)doInit {
self.playbackRates = @[@"0.5", @"1.0", @"1.25", @"1.5", @"2.0", @"3.0"];
}
#pragma mark - Override Methods
#pragma mark - Public Methods
- (NSArray *)changePlaybackRateWithRateArray:(NSArray *)rateArray {
if (![self shouldChange:rateArray]) {
return rateArray;
}
// NSLog(@"[%@] change before rateArray = %@, class:%@", nj_logPrefix, rateArray, NSStringFromClass([rateArray class]));
NSInteger rateCount = rateArray.count;
NSInteger newRateCount = self.playbackRates.count;
NSInteger count = MIN(rateCount, newRateCount);
for (NSInteger i = 0; i < count; i++) {
BBPlayerPlayerRateModel *rateModel = rateArray[i];
NSString *newRateStr = self.playbackRates[i];
rateModel.value = newRateStr.doubleValue;
rateModel.text = newRateStr;
}
// NSLog(@"[%@] change after rateArray = %@, class:%@", nj_logPrefix, rateArray, NSStringFromClass([rateArray class]));
return rateArray;
}
#pragma mark - Private Methods
- (BOOL)shouldChange:(NSArray *)rateArray {
BOOL flag = YES;
for (BBPlayerPlayerRateModel *item in rateArray) {
if (item.value == NJChangePlaybackRateFlag) {
flag = NO;
break;
}
}
return flag;
}
@end
Hook 实现
swift
// 声明原函数类型
public typealias orig_landscapeVideo_fullScreenPlayback_RateModelArr_type = @convention(c) () -> Int64
// 定义全局函数指针变量,并绑定一个 C 名字
@_silgen_name("orig_landscapeVideo_fullScreenPlayback_RateModelArr")
nonisolated(unsafe) public var orig_landscapeVideo_fullScreenPlayback_RateModelArr: orig_landscapeVideo_fullScreenPlayback_RateModelArr_type? = nil
// [横屏视频-全屏播放]播放速度数组
@_cdecl("my_landscapeVideo_fullScreenPlayback_RateModelArr")
func my_landscapeVideo_fullScreenPlayback_RateModelArr() -> Int64 {
if let orig_landscapeVideo_fullScreenPlayback_RateModelArr {
let origPtr = orig_landscapeVideo_fullScreenPlayback_RateModelArr()
let origArr = unsafeBitCast(origPtr, to: [NSObject].self)
let tool = NJChangePlaybackRateTool()
tool.changePlaybackRate(withRateArray: origArr)
let retPtr = unsafeBitCast(origArr, to: Int64.self)
return retPtr
}
return 0
}
objc
// __int64 sub_10D82E870()
// [横屏视频-全屏播放]播放速度数组
long long landscapeVideo_fullScreenPlayback_RateModelArr_address = g_slide+0x10D82E870;
NSLog(@"[%@] cal func landscapeVideo_fullScreenPlayback_RateModelArr_address address:0x%llx", nj_logPrefix, landscapeVideo_fullScreenPlayback_RateModelArr_address);
MSHookFunction((void *)landscapeVideo_fullScreenPlayback_RateModelArr_address,
(void*)my_landscapeVideo_fullScreenPlayback_RateModelArr,
(void**)&orig_landscapeVideo_fullScreenPlayback_RateModelArr);
非越狱解决方案
通过 IDA Pro 修改汇编指令,直接硬编码新倍速。
分析
- 我们知道
sub_10D82E870方法返回的是BBPlayerPlayerRateModel数组,而BBPlayerPlayerRateModel的结构如下,所以我们要修改BBPlayerPlayerRateModel的value和text的值
objc
@interface BBPlayerPlayerRateModel : NSObject // (Swift)
@property (nonatomic) double value;
@property (nonatomic, copy) NSString *text;
/* instance methods */
- (id)initWithValue:(double)value text:(id)text;
- (id)init;
@end
修改value
让chatgpt帮忙分析sub_10D82E870方法的汇编指令,给我们赋值value的地址,给出如下表格:
| 元素 index | 地址 | 旧值: 旧汇编指令 | 新值: 新汇编指令 |
|---|---|---|---|
| 0 | 0x10D82E8FC |
"0.5": MOV X8, #0x3FE0000000000000 |
不变 |
| 1 | 0x10D82E970 |
"0.75": MOV X8, #0x3FE8000000000000 |
"1.0": MOV X8, #0x3FF0000000000000 |
| 2 | 0x10D82E9E0 |
"1.0": MOV X8, #0x3FF0000000000000 |
"1.25": MOV X8, #0x3FF4000000000000 |
| 3 | 0x10D82EA4C |
"1.25": MOV X8, #0x3FF4000000000000 |
"1.5": MOV X8, #0x3FF8000000000000 |
| 4 | 0x10D82EAB8 |
"1.5": MOV X8, #0x3FF8000000000000 |
"2.0": MOV X8, #0x4000000000000000 |
| 5 | 0x10D82EB28 |
"2.0": MOV X8, #0x4000000000000000 |
"3.0": MOV X8, #0x4008000000000000 |
示例
-
我们修改
000000010D82E970的指令objc__text:000000010D82E970 MOV X8, #0x3FE8000000000000- 鼠标点击
000000010D82E970
objc__text:000000010D82E970 MOV X8, #0x3FE8000000000000- 右键选择
Assemble
- 改成
objcMOV X8, #0x3FF0000000000000
- 点击
enter
- 鼠标点击
-
保存到
Mach-O文件中IDA->Edit->Patch program->Apply patches to input file->Apply patches


-
保存后:底部会显示
log:objcPatch successful: /Users/touchworld/Documents/iOSDisassembler/hook/bilibili/IDA_max_0/bili-universal

-
验证修改结果
- 将修改后的
Mach-O文件放入.app文件中 Xcode添加符号断点sub_10D82E870

sub_10D82E870断点触发,打印它的返回值,发现元素1 的value已经从0.75改为了1.0,验证修改成功。
objc(lldb) po (id)$x0 <_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x303adb570>( <BBPlayerPlayerRateModel: 0x301d18f60; value = 0.500000; text = 0.5>, <BBPlayerPlayerRateModel: 0x301d19880; value = 1.000000; text = 0.75>, <BBPlayerPlayerRateModel: 0x301d197e0; value = 1.000000; text = 1.0>, <BBPlayerPlayerRateModel: 0x301d19840; value = 1.250000; text = 1.25>, <BBPlayerPlayerRateModel: 0x301d19780; value = 1.500000; text = 1.5>, <BBPlayerPlayerRateModel: 0x301d19860; value = 2.000000; text = 2.0> ) - 将修改后的
-
全部修改结果
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x3009c9360>(
<BBPlayerPlayerRateModel: 0x3028a4cc0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x3028a5660; value = 1.000000; text = 0.75>,
<BBPlayerPlayerRateModel: 0x3028a49a0; value = 1.250000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x3028a4900; value = 1.500000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x3028a48a0; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x3028a4400; value = 3.000000; text = 2.0>
)
修改text
让chatgpt分析sub_10D82E870方法的汇编指令,给我们赋值text的地址,给出如下表格:
| 元素 index | 地址 | 旧值: 旧汇编指令 |
|---|---|---|
| 0 | 0x10D82E928 |
0.5: MOV W8, #0x352E30 |
| 1 | 0x10D82E998 |
0.75: MOV W8, #0x35372E30 |
| 2 | 0x10D82EA08 |
1.0: MOV W28, #0x302E31 |
| 3 | 0x10D82EA74 |
1.25: MOV W8, #0x35322E31 |
| 4 | 0x10D82EAE0 |
1.5: MOV W8, #0x352E31 |
| 5 | 0x10D82EB50 |
2.0: ADD X8, X28, #1 |
分析
第5个元素的0x10D82EB50的指令是ADD X8, X28, #1,而x28寄存器只在__text:000000010D82EA08 MOV W28, #0x302E31赋过值,而#0x302E31的值是1.0,所以ADD X8, X28, #1相当于是X8 = X28 + 1 = 1.0 + 1 = 2.0 ,所以第5个元素显示的是2.0。
所以方案就是,1.0要使用MOV W28, #0x302E31,2.0使用ADD X8, X28, #1,3.0跟2.0一致。
下面在元素1中,给出具体的调试过程,其他元素类似。
元素0
元素0不需要修改,就是0.5
元素1
-
原本
0x10D82E998的MOV W8, #0x35372E30(0.75),占两个指令空间,要改成MOV W28, #0x302E31(1.0)- 修改前
objc__text:000000010D82E998 MOV W8, #0x35372E30- 修改后
objc__text:000000010D82E998 MOV W28, #0x302E31
伪指令
伪指令 (Pseudo-Instruction)不是 CPU 真实存在的机器指令,而是编译器/汇编器提供的"方便写法"。
最终汇编器会把伪指令转换成 一条或多条真实的机器指令。
看个例子:
- 鼠标点击
000000010D82E998
objc
__text:000000010D82E998 MOV W8, #0x35372E30
- 查看
000000010D82E998的16进制视图,发现指令占用了8 bytes,也就是两个指令的空间,所以MOV W8, #0x35372E30是一个伪指令

- 问
chatgpt,MOV W8, #0x35372E30可以拆分成什么指令?回答如下
objc
MOVZ W8, #0x2E30
MOVK W8, #0x3537, LSL #16
获取伪指令的真实指令
- 问
chatgpt,MOV W28, #0x302E31可以拆分成:
objc
MOVZ W28, #0x2E31 // 写入低 16 位
MOVK W28, #0x0030, LSL #16 // 写入高 16 位
获取汇编指令的机器码
- 写
main.S文件
objc
.global _main
_main:
MOVZ W28, #0x2E31 // 写入低 16 位
MOVK W28, #0x0030, LSL #16 // 写入高 16 位
ret
- 编译
main.S文件
objc
clang -arch arm64 main.S -o main

- 使用
MachOView查看main文件(Mach-O文件)的机器码

- 所以
MOV W28, #0x302E31的机器码是
objc
3C C6 85 52 1C 06 A0 72
修改指令的机器码
- 鼠标点击
000000010D82E998 IDA->Edit->Patch program->Change byte

- 显示
Patch Bytes弹框

- 从
Origin value:- 08 C6 85 52 E8 A6 A6 72 1B 80 FC D2 C8 6E 00 A9
- 修改 Values 为:
- 3C C6 85 52 1C 06 A0 72 1B 80 FC D2 DC 6A 00 A9
- 点击
OK,真正修改

保存
-
保存到
Mach-O文件中IDA->Edit->Patch program->Apply patches to input file->Apply patches


- 保存后:底部会显示
log:
objc
Patch successful: /Users/touchworld/Documents/iOSDisassembler/hook/bilibili/IDA_max_0/bili-universal

验证失败
- 将修改后的
Mach-O文件放入.app文件中 Xcode添加符号断点sub_10D82E870

sub_10D82E870断点触发,打印它的返回值,发现元素1 的text已经从"0.75"改为了"1.0",但是打印的数组显示不全,而且倍速列表显示的是1.0,不是1.0X,所以还是有问题。
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x3036c0190>(
<BBPlayerPlayerRateModel: 0x301058d80; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x30105b380; value = 1.000000; text = 1.0

处理问题
- 我们知道
0x10D82EA08的MOV W28, #0x302E31的值是1.0,它的下一条指令是STP X28, X26, [X22]
objc
__text:000000010D82EA08 MOV W28, #0x302E31
__text:000000010D82EA10 STP X28, X26, [X22]
- 而修改后的
000000010D82E998的MOV W28, #0x302E31,它的下一条指令是STP X8, X27, [X22]
objc
__text:000000010D82E998 MOV W28, #0x302E31
__text:000000010D82E9A4 STP X8, X27, [X22]
- 我们尝试将
0x10D82E9A4的STP X8, X27, [X22],改为STP X28, X26, [X22]

再次验证
sub_10D82E870断点触发,打印它的返回值,发现元素1 的text已经从"0.75"改为了"1.0",而且数组全部打印了,倍速列表显示的是1.0X,修改成功。
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x301b6b8e0>(
<BBPlayerPlayerRateModel: 0x303bbd1c0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x303bbd200; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x303bbcf80; value = 1.250000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x303bbcf40; value = 1.500000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x303bbcb20; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x303bbc860; value = 3.000000; text = 2.0>
)

元素2
-
原本
0x10D82EA08的MOV W28, #0x302E31(1.0),占两个指令空间,要改成MOV W8, #0x35322E31(1.25)- 修改前
objc__text:000000010D82EA08 MOV W28, #0x302E31- 修改后
objc__text:000000010D82EA08 MOV W8, #0x35322E31 -
MOV W8, #0x35322E31拆分成:
objc
MOVZ W8, #0x2E31 // 写低 16 bit
MOVK W8, #0x3532, LSL #16 // 写高 16 bit
MOV W8, #0x35322E31的机器码
objc
28 C6 85 52 48 A6 A6 72
-
还需要将
0x10D82EA10的STP X28, X26, [X22],占一个指令空间,改成STP X8, X27, [X22]- 修改前
objc__text:000000010D82EA10 STP X28, X26, [X22]- 修改后
objc__text:000000010D82EA10 STP X8, X27, [X22] -
验证
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x3022f5f90>(
<BBPlayerPlayerRateModel: 0x30057cca0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x30057f360; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x30057ccc0; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x30057f3c0; value = 1.500000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x30057c960; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x30057ca00; value = 3.000000; text = 2.0>
)
元素3
-
原本
0x10D82EA74的MOV W8, #0x35322E31(1.25),占两个指令空间,要改成MOV W8, #0x352E31(1.5)- 修改前
objc__text:000000010D82EA74 MOV W8, #0x35322E31- 修改后
objc__text:000000010D82EA74 MOV W8, #0x352E31 -
MOV W8, #0x352E31拆分成
objc
MOVZ W8, #0x2E31 // 写低16位
MOVK W8, #0x0035, LSL #16 // 写高16位
MOV W8, #0x352E31的机器码
objc
28 C6 85 52 A8 06 A0 72
-
还需要将
0x10D82EA7C的STP X8, X27, [X22],占一个指令空间,改成STP X8, X26, [X22]- 修改前
objc__text:000000010D82EA7C STP X8, X27, [X22]- 修改后
objc__text:000000010D82EA7C STP X8, X26, [X22] -
验证
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x300d97610>(
<BBPlayerPlayerRateModel: 0x302ac6ac0; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x302af3760; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x302af3420; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x302af3b40; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x302af2de0; value = 2.000000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x302af3d00; value = 3.000000; text = 2.0>
)
元素4
-
原本
0x10D82EAE0的MOV W8, #0x352E31(1.5)(占三个指令空间) ,要改成MOV W8, #0x302E32(2.0)(占两个指令空间) + 一个NOP(占一个指令空间)- 修改前
objc__text:000000010D82EAE0 MOV W8, #0x352E31- 修改后
objc__text:000000010D82EAE0 MOV W8, #0x302E32 __text:000000010D82EAE8 NOP -
MOV W8, #0x302E32拆分成
objc
MOVZ W8, #0x2E32 // 写低 16 位
MOVK W8, #0x0030, LSL #16 // 写高 16 位
MOV W8, #0x302E32的机器码
objc
48 C6 85 52 08 06 A0 72
NOP的机器码
objc
1F 20 03 D5
- 总的机器码
objc
48 C6 85 52 08 06 A0 72 1F 20 03 D5
- 验证
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x303fe4f00>(
<BBPlayerPlayerRateModel: 0x3018ffe60; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x3018ffe80; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x3018ffa20; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x3018ffb20; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x3018ffb00; value = 2.000000; text = 2.0>,
<BBPlayerPlayerRateModel: 0x3018ff860; value = 3.000000; text = 2.0>
)
元素5
-
原本
0x10D82EB50的ADD X8, X28, #1(2.0),占一个指令空间,要改成ADD X8, X28, #2(3.0)- 修改前
objc__text:000000010D82EB50 ADD X8, X28, #1- 修改后
objc__text:000000010D82EB50 ADD X8, X28, #2 -
验证
objc
(lldb) po (id)$x0
<_TtGCs23_ContiguousArrayStoragePs9AnyObject__$ 0x301eb6f80>(
<BBPlayerPlayerRateModel: 0x30393ac20; value = 0.500000; text = 0.5>,
<BBPlayerPlayerRateModel: 0x30393ac40; value = 1.000000; text = 1.0>,
<BBPlayerPlayerRateModel: 0x30393ac60; value = 1.250000; text = 1.25>,
<BBPlayerPlayerRateModel: 0x30393ac80; value = 1.500000; text = 1.5>,
<BBPlayerPlayerRateModel: 0x30393aca0; value = 2.000000; text = 2.0>,
<BBPlayerPlayerRateModel: 0x30393acc0; value = 3.000000; text = 3.0>
)
效果
横屏全屏模式下,倍速菜单成功显示并支持 3.0 倍速播放。

总结
- 横屏全屏场景下,播放倍速列表由
BBPlayerPlaybackRateListWidget的rateArray生成,最终创建函数为sub_10D82E870。 - 通过 越狱 Hook 或 非越狱 Mach-O Patch ,即可稳定新增
3.0x倍速。