抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

困扰 80% 玩家,GTA 5 祖传 7 年的加载时间问题,现被一玩家缩短了 70%

2021 年 3 月 19 日

困扰80%玩家,GTA 5祖传7年的加载时间问题,现被一玩家缩短了70%

3 月初,游戏玩家 tostercx 宣称发明了一种新方法,可以把《侠盗猎车手 Online》(检查 GTA 5)的加载时间缩短 70%。对玩家群体来说,这无疑是一个好消息。


一直以来,《侠盗猎车手 Online》因慢如蜗牛的加载时间已经“臭名昭著”。社交媒体 Reddit 上有一份针对这个问题的调查,结果发现超过 80%的玩家都受到加载时间太慢的困扰。关键是,这个问题已经存在了 7 年,而游戏开发商 Rockstar Games 却没有给出任何解决方案。


而 tostercx 决心深究,他发现加载时间慢的问题在于启动《侠盗猎车手 Online》时存在单线程 CPU 瓶颈,并且游戏在费劲地解析 10MB 的 JSON 文件。对此,他认为,解决这款游戏加载时间慢的问题,只需要一名开发人员不到一天的时间。


随后,他写了一篇技术文章发在博客上,引起很大反响,不仅被 Hacker News 置顶,而且网友纷纷转发,甚至获得游戏开发商 Rockstar 10000 美元的奖励。


Rockstar 在官方通告中称,“经过彻底的调查,我们可以确定玩家 tostercx 确实揭示了《侠盗猎车手 Online》PC 版加载时间有待改善的游戏代码问题。作为调查结果,我们做了一些改进,这些改进会在更新中得到应用。”

慢如蜗牛的加载时间


作为《侠盗猎车手 Online》玩家,tostercx 不久前又上线打了几个任务,但是他发现这款游戏的加载时间仍然和 7 年前刚发布的时候一样慢。


于是,他去网上搜索,想了解是否已经有人解决了这个问题。而网上大多数的结果都是一些个人评论和猜测,比如这款游戏太复杂了所以加载很慢,p2p 网络架构太垃圾了所以加载快不了,等等。


当然,还有一些提升加载速度的建议,比如先载入故事模式,然后玩一次单人任务;另外,还有几个可以跳过启动 logo 视频的 mod。


但是,这些办法结合起来只能节省 10-30 秒。而他玩这款游戏,所需的故事模式加载时间是 1 分 10 秒,而在线模式加载时间是 6 分钟。详情如下:


故事模式加载时间:  ~1m 10s在线模式加载时间: ~6m 左右禁用启动菜单, 从 R* logo 到进入游戏内的时间 (social club 登录时间不算).
老当益壮的 CPU: AMD FX-8350便宜的 SSD: KINGSTON SA400S37120G当然得有内存: 2x Kingston 8192 MB (DDR3-1337) 99U5471不错的 GPU: NVIDIA GeForce GTX 1070
复制代码


tostercx 在博客写道,“我知道我的机器配置已经过时,但是加载到在线模式竟然需要 6 倍左右的时间?即使用上别人总结的从故事模式切换到在线模式的加载技巧,也没产生什么效果。”

开始调查


Reddit 的一份调查表明,超过 80%的玩家都受到这个问题的困扰。



只有 20%的玩家所用的加载时间不到 3 分钟,而他们极有可能是因为配备了高端游戏 PC。根据 tostercx 的基准测试,高端游戏 PC 加载在线模式,只需要大约 2 分钟。


tostercx 发现:即使如此,他们的故事模式还是需要近 1 分钟的加载时间?他们从故事模式切换到在线模式只需一分多钟。


“我知道,他们的硬件配置要好很多,但肯定不会好 5 倍那么厉害。”他说。

继续研究


利用任务管理器之类的强大工具,tostercx 开始研究哪些资源可能成为瓶颈。



游戏花了一点时间加载用于故事模式和在线模式的通用资源,这部分时间与高端 PC 的耗时差不多。然后,游戏在他的计算机上拉满一个核心跑 4 分钟时间,这 4 分钟其他什么事都不干。


正如上图所示,磁盘利用率为 0,网络利用率在几秒钟后也降为 0,GPU 利用率为 0,而内存使用,则完全是一条直线。


tostercx 表示,“它是在挖掘加密货币还是在干什么勾当?我闻到代码的味道了。真的是很糟糕的代码。”

问题根因


在 tostercx 看来,虽然自己的机器很老(老旧的 AMD CPU 有 8 个核心),制造时间也有很久,但是这可能无法解释所有的加载时间差异。


让他感到奇怪的是,游戏只占用了 CPU 资源。他以为会有大量的磁盘读取过程来加载资源,或很多网络请求负载尝试在 p2p 网络中协商会话。


“但是现在这样?这可能是一个 bug。”

Profiling

Profiler(分析器)是查找 CPU 瓶颈的好工具。这里只有一个问题——大多数 Profiler 都需要检测源代码,才能对程序运行过程中所发生的事情有一个完整的了解。


而 tostercx 没有源代码,也不需要微秒级的读数——其瓶颈长达 4 分钟。


然后,了解一下堆栈采样:对闭源应用程序来说,Profiler 只有一个选项。转储正在运行的进程的堆栈和当前指令指针的位置,以按设置的时间间隔构建一个调用树。然后将它们加起来以获取当前状况的统计信息。


据他了解,只有一个 Profiler 可以在 Windows 上执行这些操作,而且它已经十多年没有更新了。它就是 Luke Stackwalker!



一般来说,Luke 会将相同的函数归为一组,但由于他没有调试符号,因此不得不盯着附近的地址来猜测它是否在同一位置。那么我们看到了什么?不是一个瓶颈,而是两个!

继续深入

tostercx 借用了其朋友的行业标准级反汇编程序的完全合法副本,然后把 GTA 拆开来看个究竟。



这根本不对头。大多数著名游戏都带有针对逆向工程的内置保护措施,可以防止盗版、作弊和修改。这倒不是说这类措施一定有用。 这里似乎存在某种混淆/加密,已经用乱码代替了大多数指令。


但是,这不是关键,“我们只需要在执行我们要看的部分时转储游戏的内存即可”。在运行之前,必须对指令进行混淆处理,他使用了 Process Dump。

问题一:这是……strlen?!

反汇编现在不太混乱的转储会发现,其中一个地址的一个标签被拉出到了某个地方!这是 strlen?在调用堆栈中,下一个标记为 vscan_fn,此后标记结束。tostercx 认为它就是 sscanf。



它正在解析某些内容。解析什么?反汇编太花时间了,因此他决定使用 x64dbg 从正在运行的进程中转储一些样本。后来经过一些调试步骤,他发现它是……JSON!他们正在解析 JSON,一个带有约 63k 项的条目,体积高达 10MB 的 JSON


...,{    "key": "WP_WCT_TINT_21_t2_v9_n2",    "price": 45000,    "statName": "CHAR_KIT_FM_PURCHASE20",    "storageType": "BITFIELD",    "bitShift": 7,    "bitSize": 1,    "category": ["CATEGORY_WEAPON_MOD"]},...
复制代码


它是什么?根据一些参考资料,它似乎是“在线商店目录”的数据。它可能包含了你可以在 GTA Online 中购买的所有物品和升级的列表

问题二:使用哈希数组吗?

原来第二名=个罪犯和第一个是紧挨着的。就像在这段丑陋的反编译内容中看到的那样,它们甚至都在相同的 if 语句中被调用:


第二个问题?解析项目后,它立即存储在一个数组(或一个内联的 C++列表?不确定)中。每个条目如下所示:


struct {    uint64_t *hash;    item_t   *item;} entry;
复制代码


但是在存储之前?它会逐一检查整个数组,对比项目的哈希值以查看它是否在列表中。条目总共有约 63k,也就是说需要(n^2+n)/2=(63000^2+63000)/2=1984531500。可是大多数操作都没用。tostercx 表示,“既然你有唯一的哈希,为什么不使用哈希图呢?”



他在反编译时将其命名为 hashmap,但它显然 not_a_hashmap。这还没完。加载 JSON 之前,hash-array-list-thing 是空的。而且 JSON 中的所有项目都是唯一的!他们甚至不需要检查它是否在列表中!它们甚至有一个直接插入项目的函数!就用它就行了!有没有搞错!?

解决方案

tostercx 在找到问题后,写了一个**.dll,将其注入 GTA 中,并 hook 上一些函数**,搞定加载时间慢的问题。


JSON 问题比较棘手,他无法替换它们的解析器。用不依赖 strlen 的 sscanf 替换 sscanf 更为现实。但是有一种更简单的方法。


  • hook strlen

  • 等待一个长字符串

  • “缓存”它的起始和长度

  • 如果在字符串范围内再次调用它,则返回缓存的值


如下:


size_t strlen_cacher(char* str){  static char* start;  static char* end;  size_t len;  const size_t cap = 20000;  // if we have a "cached" string and current pointer is within it  if (start && str >= start && str <= end) {    // calculate the new strlen    len = end - str;    // if we're near the end, unload self    // we don't want to mess something else up    if (len < cap / 2)      MH_DisableHook((LPVOID)strlen_addr);    // super-fast return!    return len;  }  // count the actual length  // we need at least one measurement of the large JSON  // or normal strlen for other strings  len = builtin_strlen(str);  // if it was the really long string  // save it's start and end addresses  if (len > cap) {    start = str;    end = str + len;  }  // slow, boring return  return len;}
复制代码


至于哈希数组问题,其实更简单——完全跳过重复检查并直接插入项目,因为我们知道值是唯一的。


char __fastcall netcat_insert_dedupe_hooked(uint64_t catalog, uint64_t* key, uint64_t* item){  // didn't bother reversing the structure  uint64_t not_a_hashmap = catalog + 88;  // no idea what this does, but repeat what the original did  if (!(*(uint8_t(__fastcall**)(uint64_t*))(*item + 48))(item))    return 0;  // insert directly  netcat_insert_direct(not_a_hashmap, key, &item);  // remove hooks when the last item's hash is hit  // and unload the .dll, we are done here :)  if (*key == 0x7FFFD6BE) {    MH_DisableHook((LPVOID)netcat_insert_dedupe_addr);    unload();  }  return 1;}
复制代码


很快,这办法就奏效了,《侠盗猎车手 Online》加载时间缩短 70%。如下:


Original online mode load time:        ~6m flatTime with only duplication check patch: 4m 30sTime with only JSON parser patch:       2m 50sTime with both issues patched:          1m 50s(6*60 - (1*60+50)) / (6*60) = 69.4% load time improvement (nice!)
复制代码


根据这名玩家的总结:


  • 启动 GTA Online 时存在单线程 CPU 瓶颈

  • 事实证明,GTA 原来在费劲地解析 10MB 的 JSON 文件

  • JSON 解析器本身没做好,并且

  • 解析后,有一个缓慢的重复项目删除流程


附:


玩家 tostercx 的 PoC 完整资料https://github.com/tostercx/GTAO_Booster_PoC


官方更新 https://support.rockstargames.com/articles/360061161574/

2021 年 3 月 19 日 18:411462
用户头像
万佳 InfoQ编辑

发布了 617 篇内容, 共 231.9 次阅读, 收获喜欢 1561 次。

关注

评论 1 条评论

发布
用户头像
厉害了。
2021 年 03 月 22 日 14:38
回复
没有更多了
发现更多内容

自定义 View 功能上线,你的小程序可以更多变

蚂蚁集团移动开发平台 mPaaS

小程序 mPaaS 自定义控件

网络攻防学习笔记 Day54

穿过生命散发芬芳

网络攻防 6月日更

限量!Alibaba首发“Java成长笔记”,差距不止一点点

云流

Java 编程 程序员 架构 面试

企业想要升级生产管理系统,有哪些好用的低代码平台推荐?

优秀

低代码

我看JAVA 之 JVM

awen

Java JVM

看完阿里开源笔记,我终于敢说精通“网络协议”了

Java架构师迁哥

Flutter&Dart Callback转同步

小呆呆666

flutter ios android 前端 大前端

空手撸SOLID架构设计原则,六大原则层层解析,你绝想不到

Java 白

Java MySQL java程序员

与其摸鱼,不如来看:高性能消息中间件NSQ解析的整体介绍

Java 白

Github上星标85k的,图解操作系统、网络、计算机 PDF,竟是阿里的?

Java架构师迁哥

Quick BI的可视分析之路

数智化转型俱乐部

阿里云 数据中台 数据分析 数据可视化 商业分析

一周信创舆情观察(6.14~6.20)

统小信uos

【国际奥林匹克日】和TcaplusDB君一起动起来!

tcaplus

数据库 游戏 TcaplusDB

Flutter Webview添加Cookie的正确姿势

小呆呆666

flutter ios android 前端 大前端

阿里内部最新Java面试解析(全彩版)开源!(分布式/中间件/高并发/设计模式全都有)

程序员小毕

Java 程序员 架构 面试 分布式

Visual Studio 2010下ASPX页面的TreeView控件循环遍历

DisonTangor

C#

与8090创业者、投资人共话“初心”!2021中国新青年创业投资峰会举办

创业邦

【国际禁毒日】和TcaplusDB一起向毒品say NO!

tcaplus

数据库 TcaplusDB

Python接口自动化之常见用例读取方法介绍

行者AI

测试 #python

腾讯同事内推的那位Linux C/C++后端开发同学面试没过......

Linux服务器开发

Linux C/C++ Linux服务器开发 Linux后台开发 Linux网络编程

4个改变你编程技能的小技巧,建议细读

欢喜学安卓

android 程序员 面试 移动开发

数据校检

若尘

计算机组成原理 六月日更

fish_redux使用详解---看完就会用!

小呆呆666

flutter ios android 前端 社区

阿里云中间件首席架构师李小平:企业为什么需要云原生?

阿里巴巴云原生

“阿里爸爸”又出全新大厂面试参考指南,GitHub点赞20k仅是开始

Crud的程序员

Java 程序员 架构

仿imtoken钱包源码开发,imtoken去中心化钱包开发

13823153121

windows11泄露版尝鲜体验新功能!!!

学神来啦

win10 win11 windows10 windows 11

33岁跳槽无路,濒临绝望之际受贵人指点,成功上岸阿里(Java岗)

互联网架构师小马

Java 面试 阿里 中年危机 大龄程序员

4面字节跳动拿到Offer,灵魂拷问

欢喜学安卓

android 程序员 面试 移动开发

最强大的内在激励:自我承诺

石云升

激励 职场经验 管理经验 6月日更

蜜雪冰城主题曲血洗B站:企业自媒体运营如何接地气

石头IT视角

Study Go: From Zero to Hero

Study Go: From Zero to Hero

困扰80%玩家,GTA 5祖传7年的加载时间问题,现被一玩家缩短了70%-InfoQ