本文为 tukana 经「其乐」发布的原创(翻译)文章,未经许可,严禁转载
本帖最后由 tukana 于 2021-3-1 21:19 编辑
今天Hacker News首页第一名的文章:《如何将GTA Online的加载时间缩短70%》
原作者应该是很久没玩过GTA线上模式了,他最近想上游戏看看新增了哪些内容,却发现这个模式的加载过程依然像六年前那样十分漫长。作者翻看了一些网上的讨论,都将这个问题归因于“游戏内容过于庞大”,或者“P2P网络的原因”,但他觉得这个问题并没有那么简单,于是想一探究竟。
作者首先用任务管理器简单查看了线上模式的整个加载过程。他奇怪地发现,在完成单人游戏内容的加载后,游戏的多人部分加载仅仅是在利用CPU的单核性能,几乎没有磁盘读取、没有内存写入、也没有网络活动。要知道对于一个网络游戏来说,这一点非常奇怪。
由于无法看到源代码,作者接下来使用一个十年前的古老工具Luke Stackwalker来进行堆栈采样,试图从调用树进行分析。他发现,造成性能瓶颈的原因不是一个,而是两个(但此时还不知道具体的原因是什么)。
随后,在借助了工业界级别的逆向工具,以及x64dbg和Process Dump的帮助后,作者发现了游戏在数据结构上存在着低级缺陷:
低级缺陷一:
1、线上模式在加载时,会从大小为10MB左右JSON数据中解析出游戏内所有可购买的物品,数量大概为63000个;
2、编写这部分解析代码的某位天才工程师,在代码中使用的是sscanf()和strlen(),更为奇葩的是,他可能使用的取值方法,是在这个10MB大小的字符串中进行逐个字符的遍历。
低级缺陷二:
1、将某个物品从JSON中解析出来之后,游戏会将它存储在数组或者列表中(事实上这一步是完全没必要的,见后);
2、然而在存储之前,这位(或另一位)天才工程师遍历了整个数组,并且根据物品的哈希值来进行重复性检查。那么,既然每个物品都有不同的哈希值,为什么不使用hash map呢?
3、事实上,由于在加载JSON之前,整个所谓的哈希数组都是空的,而且每个物品的哈希值都不同,那么根本就不需要检查这件物品是否在数组中!更夸张的是,R星的这位(或另一位)天才工程师甚至还专门写了个函数来处理物品的插入。
基于以上分析,原作者给出了解决这两个问题的方案:
1、将strlen()进行hook;
2、等待长字符串;
3、将字符串的开始和结束进行“缓存”;
4、如果在字符串的范围内再次被调用,返回缓存值。
对于上面讨论的缺陷二,直接跳过重复性检查即可。
完整的代码:https://github.com/tostercx/GTAO_Booster_PoC
如果有兴趣的话,你也可以自行使用这个补丁:git clone以上代码后,使用MSVC编译整个工程得到DLL文件,然后在游戏启动时,使用DLL注入器将其注入到游戏中。
注意:由于涉及到和外挂类似的DLL注入,因此风险自负。
在使用这个补丁后,线上模式的加载时间减少了70%。
1、线上模式启动时存在CPU单核性能的大量占用;
2、分析发现其原因是游戏在加载时,会解析一个10MB左右大小的JSON数据;
3、R星某位天才工程师将这个解析器做得十分低效;
4、解析之后还存在一个完全没有必要的物品重复性检查。
查看原博文可以点击此处,里面有更详细的技术细节。
这篇文章在今天的Hacker News上位居热度第一,里面的讨论也颇为热烈。显然,当一款游戏加载时,大多数玩家并不会关心这个过程的背后,如果时间太慢,也只会觉得这个游戏过于庞大,或者自己的机器性能不好。但对于GTA5这种体量的游戏,仅仅是因为一个菜鸟级别的数据处理方法,就让全世界数以千万的玩家在这六年里浪费了70%的加载时间,不由得让人感叹。不知道R星是否会推出后续补丁来修复这个问题。
|