困扰80%玩家,祖传7年的加载时间问题,被一玩家缩短70%
qiyuwang 2024-10-18 10:22 12 浏览 0 评论
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 flat
Time with only duplication check patch: 4m 30s
Time with only JSON parser patch: 2m 50s
Time 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/
相关推荐
- # 安装打开 ubuntu-22.04.3-LTS 报错 解决方案
-
#安装打开ubuntu-22.04.3-LTS报错解决方案WslRegisterDistributionfailedwitherror:0x800701bcError:0x80070...
- 利用阿里云镜像在ubuntu上安装Docker
-
简介:...
- 如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本
-
UbuntuKylin系统使用一段时间后,有新的版本发布,如何将现有的UbuntuKylin系统升级到最新版本?可以通过下面的方法进行升级。1.先查看相关的UbuntuKylin系统版本情况。使...
- Ubuntu 16.10内部代号确认为Yakkety Yak
-
在正式宣布Ubuntu16.04LTS(XenialXerus)的当天,Canonical创始人MarkShuttleworth还非常开心的在个人微博上宣布Ubuntu下个版本16.10的内...
- 如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)
-
在Windows11的WSL(WindowsSubsystemforLinux)上安装Ubuntu非常简单。以下是详细的步骤:---...
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
-
IT之家2月18日消息,科技媒体pureinfotech昨日(2月17日)发布博文,介绍了3中简便的方法,让你轻松在Windows11系统中,使用WindowsSubs...
- 如何查看Linux的IP地址(如何查看Linux的ip地址)
-
本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。...
- 怎么看电脑系统?(怎么看电脑系统配置)
-
要查看电脑的操作系统信息,可以按照以下步骤操作,根据不同的操作系统选择对应的方法:一、Windows系统通过系统属性查看右键点击桌面上的“此电脑”(或“我的电脑”)图标,选择“属性”。在打开的...
- 如何查询 Linux 内核版本?这些命令一定要会!
-
Linux内核是操作系统的核心,负责管理硬件资源、调度进程、处理系统调用等关键任务。不同的内核版本可能支持不同的硬件特性、提供新的功能,或者修复了已知的安全漏洞。以下是查询内核版本的几个常见场景:...
- 深度剖析:Linux下查看系统版本与CPU架构
-
在Linux系统管理、维护以及软件部署的过程中,精准掌握系统版本和CPU架构是极为关键的基础操作。这些信息不仅有助于我们深入了解系统特性、判断软件兼容性,还能为后续的软件安装、性能优化提供重要依据。接...
- 504 错误代码解析与应对策略(504错误咋解决)
-
在互联网的使用过程中,用户偶尔会遭遇各种错误提示,其中504错误代码是较为常见的一种。504错误并非意味着网站被屏蔽,它实际上是指服务器在规定时间内未能从上游服务器获取响应,专业术语称为“Ga...
- 猎聘APP和官网崩了?回应:正对部分职位整改,临时域名可登录
-
10月12日,有网友反映猎聘网无法打开,猎聘APP无法登录。截至10月14日,仍有网友不断向猎聘官方微博下反映该情况,而猎聘官方微博未发布相关情况说明,只是在微博内对反映该情况的用户进行回复,“抱歉,...
- 域名解析的原理是什么?域名解析的流程是怎样的?
-
域名解析是网站正常运行的关键因素,因此网站管理者了解域名解析的原理和流程对于做好域名管理、解决常见解析问题,保障网站的正常运转十分必要。那么域名解析的原理是什么?域名解析的流程是怎样的?接下来,中科三...
- Linux无法解析域名的解决办法(linux 不能解析域名)
-
如果由于误操作,删除了系统原有的dhcp相关设置就无法正常解析域名。 此时,需要手动修改配置文件: /etc/resolv.conf 将域名解析服务器手动添加到配置文件中 该文件是DNS域名解...
- 域名劫持是什么?(域名劫持是什么)
-
域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的地址从而实现用户无法访问目标网站的目的。说的直白些,域名劫持,就是把互...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- # 安装打开 ubuntu-22.04.3-LTS 报错 解决方案
- 利用阿里云镜像在ubuntu上安装Docker
- 如何将Ubuntu Kylin(优麒麟)19.10系统升级到20.04版本
- Ubuntu 16.10内部代号确认为Yakkety Yak
- 如何在win11的wsl上装ubuntu(怎么在windows上安装ubuntu)
- Win11学院:如何在Windows 11上使用WSL安装Ubuntu
- 如何查看Linux的IP地址(如何查看Linux的ip地址)
- 怎么看电脑系统?(怎么看电脑系统配置)
- 如何查询 Linux 内核版本?这些命令一定要会!
- 深度剖析:Linux下查看系统版本与CPU架构
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)