У меня есть скрытое подозрение ...
Я чувствую запах рутинной процедуры обнаружения видеокарт. Это не имеет ничего общего с Windows и \ или DirectDraw (ну, частично, но не так, как вы думаете). Это просто старая игра, делающая предположения, которые больше не действительны. Это не редкость. Например, игра Oni вылетает на современных видеокартах:
Эта проблема была связана с переполнением определенного текстового буфера - тот, который перечисляет расширения OpenGL в файле startup.txt
. Когда был написан Oni, дамп списка расширений OpenGL был намного короче, и разработчики не допускали большего дампа. Современные видеокарты почти всегда вызывают это переполнение.
Нам нужно идти глубже
У меня нет Nascar Heat 2002, но я скачал демо- версию NASCAR Heat, и у него точно такая же проблема. Так что я откинул свой отладчик и дизассемблер и провел вечер, пытаясь выяснить, что не так с игрой.
На самом деле игра состоит из двух исполняемых файлов, взаимодействующих друг с другом через семафор: основной исполняемый файл (в моем случае 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
входа race.bin с помощью int3
), запустил NASCAR Heat Demo.exe
и ждал появления отладчика. И вот, когда все стало ясно.
Размер видеопамяти, возвращаемый GetVidMemSizeFromDirectDraw()
равен 0xFFFF0000
(4294901760 bytes = 4095MB
4095 МБ ), и это не имеет ничего общего с реальным (должно быть 1 ГБ на моем ПК). Оказывается, что DirectDraw не очень подходит для современной архитектуры видеокарт \ ПК
С ростом физической памяти, как RAM, так и VRAM, у этого API также возникают проблемы с копированием, поскольку он возвращает 32-битные значения DWORD размера в байтах.
и имеет тенденцию сообщать обо всем, на что это похоже:
У вас есть система с 1 ГБ или больше видеопамяти и 4 ГБ или больше системной памяти (ОЗУ).
Вы запускаете инструмент диагностики Direct-X, и он сообщает, что у вас есть неожиданно низкий объем приблизительной общей памяти на вкладке дисплея.
Вы также можете столкнуться с проблемами в некоторых играх или приложениях, не позволяющих выбрать самые подробные настройки.
API, который DXDiag использует для аппроксимации системной памяти, не был разработан для обработки систем в этой конфигурации
В системе с 1 ГБ видеопамяти следующие значения возвращаются с соответствующей системной памятью:
╔═══════════════╦═══════════════════════════════════╗
║ 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
в регистре EAX становится 0x00086000
с флагом Carry
. Если вы нажмете на значение регистра, он будет переключаться между шестнадцатеричным и десятичным представлением числа.
mov eax, 0xFFFF0000
add eax, 0x96000
Как мне это исправить?
DirectDraw вероятно , никогда не возвращать значение более чем 32-разрядное целое число может обрабатывать 1 (это , кажется, ограничен , чтобы соответствовать независимо от фактического объема памяти. Таким образом, самый простой способ решить эту проблему - удалить RawVidMemSize = RawVidMemSize + 614400
из кода, чтобы она не вызывала переполнение. В исполняемом файле это выглядит так:
- Мнемоника сборки:
add eax, 96000h
- Фактические коды операций (шестнадцатеричные):
0500600900
Чтобы удалить его, нам нужно заменить его инструкциями NOP (hex: 90
). Я уже знаю смещение файла, но оно может отличаться в вашем исполняемом файле. К счастью, шестнадцатеричная строка 0500600900
уникальна в моем race.bin
и, вероятно, в вашем . Так что возьмите hex-редактор (я рекомендую HxD: он бесплатный, портативный и простой в использовании) и откройте файл bin
.
Выполните поиск в шестнадцатеричной строке:
Как только шестнадцатеричная строка найдена
Заменить его на 90
Сохраните файл. HxD автоматически создаст резервную копию файла, которую вы сможете восстановить, если что-то пойдет не так.
В моем случае этого было достаточно, и я смог начать игру. Вот как heat.log
выглядит после патча:
21.33.564: ddraw: создан directdraw с помощью aticfx32.dll (AMD Radeon HD 5800 Series)
21.33.564: ddraw: версия 0.0.0.0
21.34.296: видео: 64 Мб (сообщено: 4095.937500)
21.34.296: vid: использование текстур AGP (3231), всего: 64
21.34.305: vid: тройной буфер включен
Если ваш файл случайно будет содержать несколько экземпляров 0500600900
, замените первый, затем попробуйте запустить игру, а если не получится, восстановите файл из резервной копии и попробуйте затем. Не заменяйте все сразу, это не очень хорошая идея.
Также было подтверждено, что такая же ошибка существует в Viper Racing. Viper Racing использует немного другое (старше?) версия игрового движка, чем Nascar, но ошибка та же: он тоже пытается добавить 614400
байт к объему видеопамяти. Значения для поиска отличаются, потому что в этом случае компилятор решил не использовать регистры и просто получил доступ к переменной из стека, то есть:
- Мнемоника сборки:
add [esp+18h+var_14], 96000h
- Фактические коды операций (шестнадцатеричные):
8144240400600900
Приятного вождения!
- Это одно из тех предположений, о которых я говорил.