为乐趣和利润修补MechCommander的"左臂漏洞"

Hacker News Top 工具

摘要

一篇详细的技术博文,解释了逆向工程和修补MechCommander 1中武器分配漏洞的过程,该漏洞会导致游戏将所有大型武器放置于机甲左臂,造成不成比例的伤害损失。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/09 07:38

# 为了乐趣和利益修补MechCommander的“左臂漏洞” – MHLoppy 来源:https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/ [](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-destroyed-Masakari-recreation-ll.webp) MechCommander 1 有一个烦人的特性:它会将所有¹(https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/#easy-footnote-bottom-1-3954)最大的武器塞进机甲的左臂。如果你的机甲主要只装备大型武器,那么失去那只手臂基本上就等于失去了整个机甲。通常这个问题对任务结果影响不大。因为精确瞄准机甲特定部位的概率相当低(即使有高射击技能驾驶员),所以对敌方机甲来说通常无关紧要;而你的机甲纯靠运气恰好失去那只手臂的几率也很低——尽管确实会发生(https://hachyderm.io/@MHLoppy/116308370578550343)。但这仍然是个奇怪的问题,而且当它发生在你身上时,可能会有点棘手。不过,现在问题解决了!在**感染**了Vana(https://mhloppy.com/2026/04/mechcommander-lbx-ac-autocannon-damage/#comments)在我脑中植入的关于“为什么分配组件的代码对大型武器似乎不起作用”的脑虫后,我花了一天时间搞明白了,现在有了一个可行的修复方案。 *(注意:我会将讨论的部位称为“左臂”,尽管还不清楚它实际上是“左臂”还是“右臂”,而且机甲失去手臂后的视觉效果并不一致。这个部位是当你使用手册中标记的“fire at left arm”命令时会被打掉的部位,所以我沿用这个说法。)* 大约一个月前,我注意到(https://mhloppy.com/2026/03/mechcommander-commando-uller-movement-speed/)MechCommander 1(至少是我拥有的MechCommander Gold版本)似乎附带某种**调试符号**(https://en.wikipedia.org/wiki/Debug_symbol),可以有限地深入了解游戏代码的实际行为。²(https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/#easy-footnote-bottom-2-3954)这意味着我们可以探究以下问题: - 哪些武器算作“大型”(仅仅是流传的“>=9吨”规则吗?) - 武器和设备是如何分布在机甲上的? - 为什么“大型”武器偏偏都被塞进左臂? #### 长官,这个Long Tom从下面看真的好大——我觉得可以叫它“大型” 游戏exe文件本身是机器码(或者如果先反编译,则是**汇编语言**(https://en.wikipedia.org/wiki/Assembly_language))。但借助Ghidra(https://github.com/NationalSecurityAgency/ghidra)(没错,就是由**妙极了**(https://en.wikipedia.org/wiki/PRISM)的NSA那帮人开发的)等工具,我们也可以将各个函数翻译成类C代码。这种翻译不一定能准确反映原始源码的结构,但通常比汇编更容易一眼看懂。首先,这里是一个似乎用于判断武器是否为“大型”的函数——这个分类与左臂漏洞相关。 ```c /* public long __thiscall LogMech::getWeaponLarge(unsigned char) public: long __thiscall LogMech::getWeaponLarge(unsigned char) */ long __thiscall LogMech::getWeaponLarge(LogMech *this,uchar param_1) { /* START-> G:\mcx\logistics.cpp: ? */ if (((((param_1 < 100) || (0x68 < param_1)) && ((param_1 < 0x6e || (0x71 < param_1)))) && ((((((param_1 != 'y' && (param_1 != 'z')) && (param_1 != 0x83)) && ((param_1 != 0x84 && (param_1 != 0x8d)))) && (param_1 != 0x8e)) && ((param_1 != 0x91 && (param_1 != 0x92)))))) && ((param_1 != 0x96 && ((param_1 != 0x97 && (param_1 != 0x9a)))))) { return 0; } return 1; } ``` (https://mhloppy.com/wp-content/uploads/2026/05/MC1-MCG-getWeaponLarge-ll.webp) 结合compbas.csv文件(该文件打包在MISC.FST文件内)³(https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/#easy-footnote-bottom-3-3954)中的武器ID以及其他来源(包括我自己的一些测试),我们可以将其翻译成一张“大型”和“小型”武器列表,其中“小型”指的是所有未明确定义为“大型”的武器。 | ID | 武器 | 可用性 | 吨位 | 尺寸 | |---|---|---|---|---| | 98 | Rail Gun | 仅扩展 | 30.0 | Small | | 99 | Light Gauss Rifle | 仅扩展 | 13.5 | Small | | 100 | Light Autocannon | 原版 | 9.5 | Large | | 101 | Autocannon | 原版 | 15.5 | Large | | 102 | Heavy Autocannon | 原版 | 19.5 | Large | | 103 | Light Ultra Autocannon | 原版 | 11.0 | Large | | 104 | Gauss Rifle | 原版 | 16.5 | Large | | 107 | Light LB-X Autocannon | 仅扩展 | 9.5 | Small | | 108 | LB-X Autocannon | 仅扩展 | 14.5 | Small | | 109 | Heavy LB-X Autocannon | 仅扩展 | 19.5 | Small | | 110 | C. Light Ultra Autocannon | 原版 | 8.5 | Large | | 111 | C. Ultra Autocannon | 原版 | 13.5 | Large | | 112 | C. Heavy Ultra Autocannon | 原版 | 17.5 | Large | | 113 | C. Gauss Rifle | 原版 | 13.5 | Large | | 116 | C. Light LB-X Autocannon | 仅扩展 | 8.5 | Small | | 117 | C. LB-X Autocannon | 仅扩展 | 13.5 | Small | | 118 | C. Heavy LB-X Autocannon | 仅扩展 | 17.5 | Small | | 120 | LRM Rack | 原版 | 4.0 | Small | | 123 | SRM Pack | 原版 | 3.0 | Small | | 125 | Streak SRM Pack | 原版 | 5.0 | Small | | 126 | Heavy Thunderbolt | 仅扩展 | 21.0 | Small | | 130 | C. LRM Rack | 原版 | 3.0 | Small | | 133 | C. SRM Pack | 原版 | 3.0 | Small | | 135 | C. Streak SRM Pack | 原版 | 4.75 | Small | | 139 | Large X-Pulse Laser | 仅扩展 | 13 | Small | | 140 | Laser Laser | 原版 | 9.5 | Small | | 141 | Large ER Laser | 原版 | 11.0 | Large | | 142 | Large Pulse Laser | 原版 | 12.0 | Large | | 143 | Laser | 原版 | 4.0 | Small | | 144 | Pulse Laser | 原版 | 6.0 | Small | | 145 | PPC | 原版 | 12.0 | Large | | 146 | ER PPC | 原版 | 15.5 | Large | | 147 | Heavy Flamer | 原版 | 8.0 | Small | | 150 | C. Large ER Laser | 原版 | 10.0 | Large | | 151 | C. Large Pulse Laser | 原版 | 11.0 | Large | | 152 | C. ER Laser | 原版 | 3.5 | Small | | 153 | C. Pulse Laser | 原版 | 4.0 | Small | | 154 | C. ER PPC | 原版 | 13.5 | Large | | 155 | C. Heavy Flamer | 原版 | 7.0 | Small | | 160 | Long Tom Cannon | 仅扩展 | 34.0 | Small | *(我排除了列表中几个无效武器,包括getWeaponLarge()函数中明确检查的少数武器,比如LRM 15——我假设这些只是被删减的内容,而且据我所知你在游戏中永远不会遇到。只能通过作弊获得的Sniper Cannon也视为无法访问的内容,以便于本文讨论)* 因此,第一个也是最明显的结论是:LRM是**rack**,而SRM是**pack**,这是击败Clan入侵者的关键信息。你能想象有人把它们叫做LRM pack或SRM rack吗?你不能指望内天体用命名错误的装备去打仗。第二个明显的结论是:Desperate Measures中新增的所有武器都被错误地归类为“小型”武器,因为getWeaponLarge()函数显然没有更新以考虑它们,而分类函数的默认输出(对于没有明确例外的武器)是“小型”。有几个分类在主观上有些模棱两可。Light Autocannon重9.5吨,被标记为“大型”,而同样重量的Large Laser却**不**被视为“大型”。我个人认为,如果Large Laser不算“大型”,那么Light AC也不该算;或者反过来说,如果Light AC算大型,那么Large Laser**也应该**算,但我觉得这不值得纠结,而且确实**有一些**合理的理由维持现状。⁴(https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/#easy-footnote-bottom-4-3954)这也表明,虽然“>=9吨即为大型”的规则**接近**正确,但并非完全准确——即使你将所有错误分类的Desperate Measures武器排除在外也是如此。前面提到的Large Laser(9.5吨)和Clan Light Ultra Autocannon(8.5吨)打破了这一规则。 为了验证我们的基本理解是否正确,我们可以让我们的驾驶员经历几次误伤事故。 [](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-check-unpatched-ER-large-lasers-and-right-arm-destroyed-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-check-unpatched-ER-large-lasers-and-both-arms-destroyed-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-check-unpatched-mixed-weapons-and-left-arm-destroyed-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-check-unpatched-small-weapons-and-left-arm-destroyed-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-check-heavy-LBX-left-arm-destroyed-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Left-arm-check-heavy-LBX-both-arms-destroyed-ll.webp) 这些结果支持以下结论: - “大型”武器确实只进入左臂(并且纸娃娃是镜像的……或者它们都在右臂,而纸娃娃不是镜像,同时手册错误地使用了“左臂”一词)。 - “小型”武器不会只进入左臂,而是在机甲上分布更广。 - Desperate Measures的武器被错误地归类为“小型”武器,尽管逻辑上它们应该是“大型”。 - 机甲驾驶员应该组织工会。 如果仔细观察,你可能会注意到被测试的机甲有时显示**右臂**被摧毁,而不是左臂。这似乎要么是一个视觉错误,要么是美术资源的限制(也许他们没有为每只缺失的手臂制作单独的版本?)——机甲精灵上视觉上缺失的手臂似乎会根据机甲面对的角度或者水星是否逆行而变化。而且,天啊,我可不想去修复这个游戏必须存在的**每一个**漏洞,所以我们直接忽略它。 [](https://mhloppy.com/wp-content/uploads/2026/05/Destroyed-arm-switch-left-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Destroyed-arm-switch-right-ll.webp) 另一个奇怪之处是,在Heavy LB-X测试中,Beast的生命值(能量?)条表现得好像他大部分武器都没了,尽管实际上**没有一个**消失。我以为可能会出现游戏内实际功能与UI中武器列表不同步的风险,但所有四把武器似乎都正常开火——我假设问题只是驾驶员生命值加上一些伤害,这些伤害大概是在所有四把武器所在的位置。也许侧躯干因为手臂被摧毁而受到了结构损伤,但没有在大纸娃娃上显示出来(因为它们还有装甲),只有屏幕底部的简化纸娃娃显示了? [](https://mhloppy.com/wp-content/uploads/2026/05/Turkina-higher-than-expected-damage-01-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Turkina-higher-than-expected-damage-02-ll.webp) 虽然作为一次绕路很有趣,但弄清楚纸娃娃差异的具体原因超出了范围,所以我们继续。 #### 武器的分布不由天定,而由代码决定 好了,那么——总体上——设备是如何分配到机甲各部位的?首先,要找到游戏代码中处理这个问题的地方。判断是否找到了正确代码的一种方法是:修改疑似函数,然后在游戏中观察效果。在这个例子中,我修改了疑似函数,将未知物品分配到原本不该去的地方。做出这个修改后,打掉机甲左臂的效果是立即杀死驾驶员……并且不摧毁机甲的任何武器。打掉左臂、驾驶员死亡后,我射击地面上的机甲以打掉它们的右臂,它们仍然没有损失任何武器——我猜这是修改带来的有趣副作用。我仍然有点好奇武器实际上**去了**哪些部位,但无法在这趟旅程中合理探索每一个我想到的问题,所以我跳过了回答这个(集中火力攻击特定部位非常慢——即使对静止目标也是如此,因此凭经验回答并不完全容易)。 [](https://mhloppy.com/wp-content/uploads/2026/05/Equipment-distributed-like-weapons-01-ll.webp)[](https://mhloppy.com/wp-content/uploads/2026/05/Equipment-distributed-like-weapons-02-ll.webp) 通过**调试器**(https://x64dbg.com/)追踪设备分配到机甲上的过程,表明我的修改可能至少将生命维持和引擎移到了左臂,这当然可以解释一旦这些部件消失后“肉和铁”都损失的原因。这本身很有意思,因为它表明MechCommander实际上——至少在一定程度上——使用了这种“内部设备”,而不是纯粹依赖于检查某个部位(头部/中央躯干等)的结构状况——老实说,我原本以为在这种情况下它是这样做的。作为冷知识,以下是我测试中似乎被分配到机甲各部位的组件(我没有具体追踪哪个部位收到了哪些设备): - 生命维持系统 - 2x 髋部 - 2x 肩部 - 陀螺仪 - 传感器(假设具体传感器会匹配机甲装备的型号) - 驾驶舱 - 2x 腿部致动器 - 2x 臂部致动器 - 聚变引擎 我不能保证所有这些设备真的有**任何**效果,但考虑到上面观察到的“打臂即死”现象,至少有一部分似乎对游戏产生了某种非零影响。总之,再深入探讨这个就是另一个范围之外的问题了。如果你想真正解决你试图弄清楚的问题,有时你必须积极抵御这些沿着知识森林蜿蜒小径而来的可疑任务的诱惑。 以下是似乎处理设备分配的函数开头部分。我不会通篇过一遍,因为它有几百行长,我不完全理解它做的所有事情,而且这样做又将是另一条超范围的绕路。 [](https://mhloppy.com/wp-content/uploads/2026/05/MC1-MCG-placeItem-top-ll.webp) 与我们相关的是顶部的检查,其中将`param_1`与`100`进行比较。在原版游戏中,武器ID从100开始,⁵(https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/#easy-footnote-bottom-5-3954)这提供了一些线索来推测该函数顶部处理的是非武器设备(如上面列出的那些)的放置。这一理论有现有证据支持,所以很可能至少是**大致**正确的,但彻底确认它超出了范围。 我们**真正**关心的是这个函数如何处理武器的放置:即ID >= 100的设备。 [](https://mhloppy.com/wp-content/uploads/2026/05/MC1-MCG-placeItem-bottom-ll.webp) 得益于调试符号提供了一些函数名称,如果你对调用函数的功能做一些假设,大部分高层细节都是显而易见的: 1. 检查武器是大型还是小型。 2. 如果是小型,根据一个检查这些部位小型武器的算法,分配到部位2-5。 3. 如果是大型,根据一个检查这些部位大型武器的算法,分配到部位2-5。 #### 如果我们玩转手臂,就能保住所有武器 按时间计算,从上面所述的情况到最后找出问题所在,是整件事中最长的部分,**但也并不特别有趣**,所以我只给出要点,跳过一些无聊的内容。 - getWeaponLarge()函数本身似乎按预期工作(但要注意,它的设计本身确实存在一些问题,例如对于Desperate Measures的武器),所以这似乎不是问题所在。 - getSmallWeaponCount()和getLargeWeaponCount()函数本身似乎都按预期工作,所以它们似乎也不是问题所在。 - 左臂⁶(https://mhloppy.com/2026/05/mechcommander-weapons-left-arm-bug-fix/#easy-footnote-bottom-6-3954)似乎

相似文章

Commodore 64: Mercenary

Hacker News Top

本文详细介绍了 Commodore 64 游戏 Mercenary 中的一个漏洞,该漏洞通过升降机制使玩家能够提前进入某些区域,同时还解释了游戏的渲染和内存管理技术。

修复我的吉他音箱固件

Lobsters Hottest

作者通过UART和JTAG逆向工程了Yamaha THR10c吉他音箱的固件,转储并修补了它,以修复旁路增益问题,并构建了一个API。