github akirahayabusa/ngb-pc-native-port-release v2.2.28
Ninja Gaiden Black Hurricane PC 2.2.28

36 minutes ago

《忍者龙剑传 黑之章》(含飓风版中文汉化 vb-zh-x2.1)与《忍者龙剑传》(2004)的非官方 Windows 原生移植。2.2.28。

2.2.28:Xbox 手柄踢箱子等“满档震动”恢复

玩家反馈

用 Xbox(XInput)手柄踢箱子不再震动,从 2.2.16 开始出现;砍敌人、蓄力等其他时候仍然有震动。作者用 Xbox 手柄在 2.2.16 上复现了。

原因:左右马达同时满档的震动从未发给手柄

  • 2.2.16 改了什么:游戏每帧都会设置一次手柄震动强度。以前(2.2.15 及更早)由游戏线程直接调用 Windows 的 XInput 发给手柄,每次约 0.4 毫秒,会占用游戏时间。2.2.16 改为:游戏线程只记下“最新的强度”,再唤醒一个后台线程去发。
  • 出错的地方:这个“最新的强度”用一个 32 位数保存(左马达在高 16 位,右马达在低 16 位),同时又用“全 1”(0xFFFFFFFF)表示“游戏还没设过震动”。可是左右马达都开到满档(65535、65535)时,拼出来恰好也是 0xFFFFFFFF。
  • 结果:游戏要求满档震动时,后台线程以为“还没设过”,跳过不发;5~7 帧后游戏设回 0,后台线程发现和上次发的(0)一样,也不发。所以手柄从头到尾都没动。
  • 为什么 2.2.15 没问题:2.2.15 只比较“这次和上次发的强度是否不同”,不同就发,没有“还没设过”这个特殊值,满档也照常发出。
  • 为什么偏偏是踢箱子:游戏里只有一部分震动是左右同时满档。作者踢箱子时记录的日志里,满档、持续 5~7 帧的短震动反复出现;砍敌人用的是左马达 75%(49151),一些动作是左 60% / 右 40% 等,这些都不是满档,照常发出,所以玩家感觉“只有踢箱子不震”。蓄力攻击最后满档的那一下(约 24 帧)同样受影响。

证据

为了确认,先后给玩家发了带诊断日志的测试包。两位玩家(都用 Xbox 有线手柄)的 2.2.28-t2 日志:

日志 游戏要求的震动 是否发给手柄
玩家 1 左 65535 / 右 65535(第 1553 帧) 没有发
玩家 2 左 39321 / 右 19661(第 2250 帧) 发了,XInput 返回成功
玩家 2 左 65535 / 右 65535(第 2511 帧) 没有发

两份日志都是:不是满档的照常发出,满档的一次都没发。这也解释了两位玩家体验不同:玩家 1 那局只碰到满档震动,所以完全不震;玩家 2 还碰到一次非满档震动,所以感觉“能震”。

排查中也排除了另外两种怀疑:后台线程每 2 秒检查一次空手柄槽,玩家电脑上只需 0.3~1 毫秒,不会挡住震动;手柄也没有被误判为“拔出”。

修复

  • 把“最新的强度”改用 64 位保存。真正的强度只占低 32 位,“还没设过”是 64 位全 1,两者不可能再相同。
  • 只改了这一处。发送方式、发送时机和 2.2.16~2.2.27 完全相同,游戏线程没有增加开销。
  • 修复后,所有左右同时满档的震动(不只是踢箱子)都会正常发给手柄。
  • 影响范围:只影响 Xbox / XInput 手柄。PS4、PS5、Switch 等通过 SDL 识别的手柄走另一条发送路径,没有这个问题。黑之章和 2004 共用这段代码,两边都已修复。
  • 用 2.2.28-t3 测试包(与本版代码相同)实测:Xbox 手柄踢箱子恢复震动。

测试

  • 黑之章第一章基准 185 秒(集显 Iris Xe):帧时间中位数 5.16 ms(2.2.27 为 5.38 ms),2695 帧中 1 帧超时,游戏速度 99.9%。
  • Windows Defender 扫描三个程序:无威胁。

发布包

  • ngb-hurricane-pc-2.2.28-win32.zip
  • 大小:3,055,664 字节
  • SHA-256:3d8a71e71d11186d7c45e89e417443835fc271e95d861f499422e25e47f7ec5d
  • 符号包 ngb-hurricane-pc-2.2.28-win32-symbols.zip:26,963,951 字节
  • SHA-256:7063b02ce19c49458747c7d19dc13450150bc93e9fca2e7b1e058ec4b2fcfaef

作者:b站 哈雅布撒(QQ:2682557218)


English

An unofficial native Windows port of Ninja Gaiden Black (including the Hurricane Chinese translation vb-zh-x2.1) and Ninja Gaiden (2004). 2.2.28.

2.2.28: full rumble on Xbox pads (box kicks and more) is back

What players reported

With an Xbox (XInput) pad, kicking a box no longer rumbled, starting with 2.2.16; hitting enemies, charging and other moves still rumbled. The author reproduced it on 2.2.16 with an Xbox pad.

Cause: rumble with both motors at full never reached the pad

  • What 2.2.16 changed: the game sets the pad's motor speeds every frame. Up to 2.2.15 the game thread passed them to Windows' XInput itself, about 0.4 ms each time, taken from the game. 2.2.16 has the game thread only note the latest speeds and wake a host thread that sends them.
  • The mistake: those latest speeds were kept in one 32-bit number (left motor in the top 16 bits, right in the bottom 16), with all ones (0xFFFFFFFF) meaning "the game has not set any yet". Both motors at full (65535, 65535) is also exactly 0xFFFFFFFF.
  • Result: when the game asked for full rumble, the host thread took it for "none yet" and skipped it; 5-7 frames later the game set 0 again, equal to what was sent last (0), so that was skipped too. The pad never moved.
  • Why 2.2.15 was fine: it only compared the speeds with the ones it sent last and sent any change; it had no "none yet" value, so full rumble went out.
  • Why box kicks in particular: only some of the game's rumbles are full on both motors. In the author's log while kicking boxes, short full pulses of 5-7 frames came again and again; hits use the left motor at 75% (49151), other moves e.g. 60% left / 40% right. None of those is full, they went out, so it felt like "only box kicks don't rumble". The full moment at the end of a charge attack (about 24 frames) was lost the same way.

Evidence

Test packages with diagnostic logging went to players. The 2.2.28-t2 logs of two players (both with wired Xbox pads):

Log Speeds the game asked for Sent to the pad?
Player 1 left 65535 / right 65535 (frame 1553) no
Player 2 left 39321 / right 19661 (frame 2250) yes, XInput returned success
Player 2 left 65535 / right 65535 (frame 2511) no

In both logs every speed short of full went out and every full one did not. That is also why the two players' experiences differed: player 1's session only had a full rumble, so nothing rumbled; player 2 also had a weaker one, so "it rumbled".

Two other suspects were ruled out: the host thread's check of empty pad slots every 2 seconds took 0.3-1 ms on the players' PCs, too short to hold up a rumble, and no pad was ever taken for unplugged.

Fix

  • The latest speeds are kept in 64 bits. Real speeds use only the low 32 bits and "none yet" is all 64 bits set, so the two can no longer be equal.
  • That is the only change. How and when the speeds are sent is the same as in 2.2.16-2.2.27; nothing is added to the game thread.
  • Every rumble with both motors at full (not only box kicks) now reaches the pad.
  • Scope: Xbox / XInput pads only. PS4, PS5, Switch and other pads found through SDL use a separate path without this value. Ninja Gaiden Black and 2004 share this code; both are fixed.
  • Tested with the 2.2.28-t3 test package (the same code as this release): box kicks rumble again on an Xbox pad.

Tests

  • Ninja Gaiden Black chapter-1 benchmark, 185 seconds (Iris Xe integrated graphics): median frame time 5.16 ms (2.2.27: 5.38 ms), 1 late frame of 2695, game speed 99.9%.
  • Windows Defender scan of the three programs: no threats.

Package

  • ngb-hurricane-pc-2.2.28-win32.zip
  • Size: 3,055,664 bytes
  • SHA-256: 3d8a71e71d11186d7c45e89e417443835fc271e95d861f499422e25e47f7ec5d
  • Symbols ngb-hurricane-pc-2.2.28-win32-symbols.zip: 26,963,951 bytes
  • SHA-256: 7063b02ce19c49458747c7d19dc13450150bc93e9fca2e7b1e058ec4b2fcfaef

Author: 哈雅布撒 on Bilibili (QQ: 2682557218)

Don't miss a new ngb-pc-native-port-release release

NewReleases is sending notifications on new releases.