我正在尝试在 Windows 8.1 笔记本电脑上运行一款老游戏(Nascar Heat 2002)。我遇到的问题是游戏在启动前崩溃,日志中报告没有可用的视频内存。这是日志文件:
41.37.114: data directory: C:\Program Files (x86)\Hasbro Interactive\NASCAR Heat\Data\
41.37.115: Config Dir: C:\Program Files (x86)\Hasbro Interactive\NASCAR Heat\
41.37.274: ddraw: created directdraw with aticfx32.dll (AMD Radeon HD 8650G + HD 8600/8700M Dual Graphics)
41.37.274: ddraw: version 0.0.0.0
41.40.904: vid: 0 meg card (reported:0.523438)
41.40.904: vid: using AGP textures (1397), total: 0
41.40.905: unsupported: 0 megs of vram"
据我所知,Windows 8.1 中包含的 DirectDraw 版本与此类旧游戏不兼容。我尝试使用 WineD3D 的库以及其他 ddraw 包装器/黑客,但无济于事。所以我的问题是:有没有办法在 Windows 或 ddraw 包装器中强制模拟一定数量的 vram(我的卡确实有视频内存),以确保此游戏能够检测到它?我已经更新到最新的催化剂驱动程序,并拥有 Microsoft DirectX 9.0c 最终用户运行时
答案1
我隐隐有种怀疑……
我从这里就能闻到显卡检测程序出了问题。这与 Windows 和/或 DirectDraw 无关(部分是相关的,但不是你想的那样)。这只是一个老游戏做出了不再有效的假设。这并不罕见。例如 Oni 游戏在现代显卡上崩溃:
这个问题可以追溯到某个文本缓冲区的溢出 - 即列出文件中 OpenGL 扩展的缓冲区
startup.txt
。编写 Oni 时,OpenGL 扩展列表转储要短得多,开发人员不允许转储更大。现代显卡几乎总是会导致这种溢出。
我们需要更深入地
我没有 Nascar Heat 2002,但我下载了一个纳斯卡热力演示它也表现出了同样的问题。所以我拿出了我的调试器和反汇编程序并花了一个晚上试图找出游戏的问题所在。
该游戏实际上由两个可执行文件组成,它们通过以下方式相互通信信号:主可执行文件(NASCAR Heat Demo.exe
在我的情况下),以及实际的游戏引擎(.\run\race.bin
)。视频卡检测例程位于race.bin
。游戏启动时,主可执行文件会复制race.bin
到 Windows TEMP 文件夹heat.bin
并从那里运行它。如果您尝试重命名race.bin
并race.exe
运行它,它会搜索应由主可执行文件创建的信号量,如果未找到,则显示此消息:
经过反汇编和快速查看字符串引用后,我发现了一个打印vid: 0 meg card (reported:0.523438)
消息的函数调用。它实际上是视频卡内存大小检测程序的一部分,伪代码如下所示(过于简单):
RawVidMemSize = GetVidMemSizeFromDirectDraw()
// Add 614400 bytes (600Kb - 640x480 mode?) to vidmem size (what for?!)
RawVidMemSize = RawVidMemSize + 614400
if (RawVidMemSize < 2000000)
{
MemSize = 0
}
else
{
if (RawVidMemSize < 4000000)
{
MemSize = 2
}
if (RawVidMemSize < 8000000)
{
MemSize = 4
}
if (RawVidMemSize < 12000000)
{
MemSize = 8
}
if (RawVidMemSize < 16000000)
{
MemSize = 12
}
if (RawVidMemSize < 32000000)
{
MemSize = 16
}
if (RawVidMemSize < 64000000)
{
MemSize = 32
}
if (RawVidMemSize > 64000000)
{
MemSize = 64
}
}
对于那些感兴趣的人,这里有来自 IDA 的函数的实际控制流以及我的注释。全尺寸图像点击。
现在是时候看看这个过程中到底发生了什么。我使用了一个经典的 break & enter 技巧(用 修补了race.bin
入口点的第一个指令int3
),启动NASCAR Heat Demo.exe
并等待调试器弹出。这时事情就变得清晰了。
从返回的视频内存大小GetVidMemSizeFromDirectDraw()
是0xFFFF0000
(4294901760 bytes = 4095MB
),它与实际大小无关(在我的电脑上应该是 1Gb)。事实证明DirectDraw 不太适合现代显卡\PC 架构
随着物理内存(RAM 和 VRAM)的增长,该 API 也遇到了应对问题,因为它返回以字节为单位的 32 位 DWORD 计数。
您的系统具有 1GB 或更大的视频内存,以及 4GB 或更大的系统内存 (RAM)。
您运行 Direct-X 诊断工具,它会报告显示选项卡上的“近似总内存”量意外地低。
您还可能会发现某些游戏或应用程序不允许您选择最高细节设置。
DXDiag 用于估算系统内存的 API 并非设计用于处理此配置的系统
在具有 1GB 视频内存的系统上,将返回与相关系统内存相关的以下值:
╔═══════════════╦═══════════════════════════════════╗
║ System Memory ║ Reported Approximate Total Memory ║
╠═══════════════╬═══════════════════════════════════╣
║ 4GB ║ 3496MB ║
║ 6GB ║ 454MB ║
║ 8GB ║ 1259MB ║
╚═══════════════╩═══════════════════════════════════╝
所以在我的情况下,它只是报告几乎适合 32 位整数的值。这就是事情变糟的地方。还记得这句话吗?
RawVidMemSize = RawVidMemSize + 614400
它变成了这样:
RawVidMemSize = 4294901760 + 614400 (= 4295516160)
并且4295516160
大于548865
32 位值可以处理的范围(0xFFFFFFFF = 4294967295
)。因此整数溢出,最终结果是548864
。所以现在,游戏认为我的 vidmem 大小过大536 千字节并拒绝奔跑。
您可以自行检查在线 x86 汇编模拟器。输入以下代码,单击左上角的复选框Windows
并选中Registers
。单击Step
按钮并0xFFFF0000
观察东芝电子寄存器变为0x00086000
带有Carry
标志。如果单击寄存器值,它将在数字的十六进制和十进制表示之间切换。
mov eax, 0xFFFF0000
add eax, 0x96000
我如何解决它?
DirectDraw 可能永远不会返回超过 32 位整数可以处理的值1(无论实际内存大小如何,它似乎都被限制以适应。因此,解决此问题的最简单方法是从RawVidMemSize = RawVidMemSize + 614400
代码中删除操作,这样它就不会触发溢出。在可执行文件中它看起来像这样:
- 汇编助记符:
add eax, 96000h
- 实际操作码(十六进制):
0500600900
要删除它,我们需要将其替换为不适用说明(十六进制90
:)。我已经知道文件偏移量,但它可能与你的可执行文件不同。幸运的是,十六进制字符串0500600900
在我的文件中是唯一的race.bin
,在你的文件中可能也是唯一的。因此,获取十六进制编辑器(我建议氢键:它是免费的、便携的并且易于使用)并打开您的bin
文件。
进行十六进制字符串搜索:
一旦找到十六进制字符串
替换为90
保存文件。HxD 将自动创建文件备份,如果出现问题,您可以恢复。
就我而言,这已经足够了,我能够开始游戏了。heat.log
补丁之后的样子如下:
21.33.564: ddraw: 使用 aticfx32.dll (AMD Radeon HD 5800 系列) 创建 directdraw
21.33.564: ddraw: 版本 0.0.0.0
21.34.296: vid:64meg 卡(已报告:4095.937500)
21.34.296: vid: 使用 AGP 纹理 (3231),总计:64
21.34.305: vid: 三重缓冲区开启
如果您的文件恰好包含多个0500600900
,请替换第一个,然后尝试启动游戏,如果不行,请从备份中恢复文件并尝试下一步。不要一次替换所有内容,这不是一个好主意。
它也曾经确认的同样的 bug 也存在于毒蛇赛车。Viper Racing 使用的游戏引擎版本与 Nascar 略有不同(较旧?),但错误是相同的:它也试图将614400
字节添加到视频内存大小。要搜索的值不同,因为在这种情况下,编译器决定不使用寄存器,而只是从堆栈访问变量,即:
- 汇编助记符:
add [esp+18h+var_14], 96000h
- 实际操作码(十六进制):
8144240400600900
祝您驾驶愉快!
- 这是其中之一假设我一直在谈论。