去除QQ聊天窗口的平滑滚动动画
本帖最后由 浩劫者12345 于 2025-3-22 02:20 编辑## 去除QQ聊天窗口的平滑滚动
QQ版本: PC经典版 9.7.23 (29394)
工具: Cheat Engine (扫内存工具), IDA (逆向工具)
QQ在大概四五年前加入的聊天窗口的平滑滚动, 即滚动鼠标滚轮时, 有一个滚动的过渡动画, 而不是一帧直接到位
但是这个动画有缺点: 因为动画帧是跑在主线程的, 当QQ特别卡的时候, 动画就会严重掉帧, 非常影响使用
本文研究如何去除滚动动画, 直接一帧到位
本文图片较多, 内容浅显, 新手应该也能看懂 (我也是逆向新手)
---
上半部分是聊天窗口的, 消息列表的在**下半部分 - [去除平滑滚动 (消息列表)](#去除平滑滚动-消息列表)**
使用 Cheat Engine 打开 QQ 进程, 打开一个聊天界面, **扫描选项选 `未知初始值`**, 进行扫描
然后将聊天页面向下滚动, 扫描选项选 `增大值`, 点下一步
重复多次, **向上滚动就筛选 `减小值`, 向下滚就筛选 `增大值`**, 直到出现两个看起来很像页面滚动高度的值为止
然后添加到地址列表
修改第一个值会发现, 这个值是滚动条的
修改第二个值没有生效, 再次滚动会被新值覆盖
我们研究第二个值, 对其**查找写入操作**
然后滚动聊天框, 找到一处代码 `AFCtrl.dll+9F643 mov ,eax`
将其添加到代码列表
点击 `显示反汇编` 打个断点玩玩看
可以发现, 打断点的情况下, 滚动一次鼠标滚轮, **第一个 tick 滚动了 `11 (0xB)` 像素, 第二个 tick 滚动到了 `90 (0x5A)` 像素**
这附近的汇编不好看懂, **我们直接上 IDA**
IDA 的地址和 CE 有点不一样, 我喜欢把二进制复制出来搜索
反编译出来可以发现是把 `a2` 赋值给了内存, 并且发现 `a2` 是该函数的入参
> 提示: 可以按 `Alt+M` 添加地址书签, 方便后续找回这个位置
> 按 `Ctrl+Shift+M` 查看地址书签
>
>
>
在函数头选择 `查找引用`, 直接点进去
发现`v3 = *a2`, `*a2` 是入参, 继续往上找
但是发现找不到直接调用的方法了
我们直接开启调试器**打断点**
选择 `附加到进程`, 选择 QQ 进程
等待一段时间加载完 symbols, 我们用地址簿找回刚刚的代码, 打断点
再次滚动聊天框, 触发断点后, 就能看**调用栈**了
往上找, 发现这里是靠指针调用的函数, 所以没法静态找出引用
这里 `a2` 又是入参, 继续往上找
终于发现 `v4` 似乎是在这个函数通过一系列计算得到的
这里看 `v4` 似乎是一堆复杂计算得到的
我反复打断点研究了好多次, 发现这个似乎是栈里面的 C++ 类, 里面有各种方法通过指针调用, 实在太烧脑了看不下去
打断点也看不出来这附近的变量跟最终滚动位置有关的
要么就是差几十个像素, 要么就是不知道为什么调用了一个函数就把值改写了
到这里重新思考一下破解思路
我们要做的是去掉平滑滚动, 也就是让它**一下子就滚动到最终位置**
那么应该有一个变量, **存放了最终滚动位置**
但是这个类是在栈里面的, 每次调用都变地址, 搜索起来极为麻烦
就在我对这一堆满天飞的指针头晕的时候, 我点开了调用栈的上一个函数...
再往上...
惊人的来了, 这个入参的 **`v14 (0xB)` 不就是当前 tick 滚动的像素数吗!**
经过多次打断点得出, **`v11 (0x5A)` 是滚动的总像素数**
下面的判断语句只有在滚动的**第一个 tick 会执行**, 用来计算第一个 tick 滚动的距离
`v12` 是上一次滚动的值
`v13` `v14` 分别是正负滚动像素数, 下面的判断语句用来判断是向上还是向下滚动
我们把那个**第一次才触发的语句强制跳过, 让它第一次就把所有像素滚完**
测试结果是非常成功的, 但是有个问题:
这个每次滚动的最终位置带有加速, 如果你鼠标滚动滚得很快, 那么聊天记录会飞得越来越快看不清
只要修改一下上面那句**获取总滚动像素的语句, 改成常量就可以了**
右键 `mov eax, `, 点汇编, 把 `` 改成常量 (比如 `100`)
按回车应用修改
现在每次鼠标滚动都是常量了
下面就能点 `编辑` - `修补程序` - `将补丁应用到...`, 输出修改后的文件了
直接放到 QQ 文件夹替换掉就能修改成功
**大功告成!**
> 另外, 按 `Ctrl+Alt+P` 可以查看已修改的字节
>
>
## 去除平滑滚动 (消息列表)
用上面一样的方法**找滚动高度值**, 找出来之后**查找写入**, 给 `GF.dll+105DFB - sub ,ecx` **打断点**
易发现**每个滚动 tick 都会触发一次断点**, 而且**每个 tick 滚动的像素数在寄存器 `ecx`**
(这里因为是对内存中的值进行减法, 所以往下滚动是减去负数, `FFFF FFE5` 转换为 uint32 是 `-27`)
这里不做汇编分析了, 直接上 IDA
发现是把 `v16` 赋给内存, `v16` 从 `a3` 来, `a3` 是入参
但是调用这个函数的地方太多了, 我们还是打断点
往上一翻就很明显了, 我们直接研究这个 `v4`
容易发现 `v4` 前面对 `*(_DWORD *)(this + 596)` 有一个**减法运算**
多打几次断点可以看出, **`v4` 是当前 tick 滚动的距离, `this + 596` 存放剩余要滚动的像素数**
那么我们的思路就是让滚动一步到位, 直接把**剩余要滚动的总像素数, 作为传参**
把 `cvttsd2si eax, qword ptr ` 改为 `mov eax, `
立即生效了
> 这里插播一个知识:
> cvttsd2si 是 `double` 转 `int`, `qword ptr` 是 64 位指针, 跟 `` 搭配使用
> 这里不能只修改地址, 否则类型就出错了, 一开始没留意发现修改之后无法滚动
如果你不追求细节, 那么修改到这里其实就可以了
但是修改完之后你再打断点会发现, 虽然滚动一步到位了但还是**重复滚动了好几个 tick, 只不过每次的值都一样**
要做完美, 就要把**多余的滚动 tick 去掉**
如果你从头开始打断点就会发现, 当滚动到最后一 tick 时, 这个 `if ( !*(_DWORD *)(this + 592) )` 会进入
可以判断这是个**计数器, 指定要滚动多少个 tick 的**
往下翻有一个地方是把 `this + 592` 清零的, 我们试试让它强制执行, 即**第一个 tick 滚动完就将计数器清零**
调整后打断点实测这个函数**只会触发一次**了
修补完这两个地方之后, 主面板的平滑滚动也没了
**至此完成!** ## 忘记说结论了
### 去除消息列表平滑滚动
`GF.dll` (9.7.21.903)
查 找: `F2 0F 2C 86 58 02 00 00`
替换为: `8B 86 54 02 00 00 90 90`
查 找: `66 0F 2F D1 72 16`
替换为: `66 0F 2F D1 90 90`
### 去除聊天窗口消息平滑滚动
注: 原版滚动存在加速, 这里修改为每次滚动100像素
`AFCtrl.dll` (9.7.23.29394)
```
查 找: 8B 86 F4 00 00 00 3B F9 73 43
替换为: B8 64 00 00 00 90 3B F9 EB 43
↑
修改此 64 (十六进制) 为你想要的每次滚动的像素值
``` 现在用QQ比较少了,但这个功能还是比较实用,感谢大佬 技术贴必须支持 非常有点技术,感谢分享 大佬太强了,很实用 讲解非常的详细,厉害了 学习了,感谢楼主 大佬太强了,必须支持! 看不懂 根本看不懂 ,天文书{:1_937:}