Я являюсь ярым фанатом игры «Дальнобойщики-2», но к сожалению, у меня не получалось запустить их под Linux. Я пытался заставить игру работать в течении нескольких лет (это не значит, что я сидел сутки напролет над ней) с переменным успехом. Пробовал разные версии wine, ковырял настройки, но игра не поддавалась. Как-то раз у меня появилось свободное время, и я решил разобраться, почему Дальнобойщики не запускаются под Wine, хотя прекрасно работали под Cedega. Далее я расскажу, как мне это удалось.
Попытка № 0. Подкручиваем конфиг
Сначала я пошел по пути наименьшего сопротивления: попробовать настроить саму игру. Мне это частично удалось. Для запуска необходимо править файл TRUCK.INI, а именно секцию DRV, и в ней прописать правильные настройки для видеоадаптера. У меня однажды получилось найти файл TRUCK.INI с правильными настройками и игра запустилась, но когда я сел писать эту статью, у меня не получилось воспроизвести запуск игры с «правильным» TRUCK.INI на непатченном wine. Считаем этот способ ненадежным.
Попытка № 1. Выбираем версию wine
Я попробовал несколько версий, которые были у меня под рукой (включая самую последнюю из git). Ни на одной из них игра не запустилась на «чистых настройках». Поэтому, решено было взять исходники последней стабильной версии. На момент написания статьи это была версия wine-1.2.3. Далее все эксперименты я проводил именно над этой версией wine.
Анализ логов игры
Когда я запускаю игру на «чистом» wine, то она показывает мне стартовый splash-screen, после чего несколько раз открывает окно, и выключается без какого либо сообщения. После такого запуска я посмотрел в папку с игрой и увидел там файл warn.log со следующим содержимым:
warning : fail in SetCoop... One or more of the parameters passed to the method are incorrect. (unknown) 0
warning : DirectDraw4/7 not found 0
warning :
(09-03-12 11:09)
Внутренняя ошибка, пожалуйста свяжитесь с отделом сопровождения
(fail in SetCoop... One or more of the parameters passed to the method are incorrect. (unknown)(null))
0
Этот лог дал мне первую подсказку: у нас неправильно инициализируется подсистема DirectDraw. Я с directdraw раньше дел не имел, поэтому эта запись мне мало чем помогла, однако это была первая зацепка. Гугл на запрос «directdraw SetCoop» выдал мне информацию о функции SetCooperativeLevel. Я не стал углубляться в описание данной функции, вместо этого я решил пойти другим путем (но к данной функции я еще вернусь).
Углубляемся в теорию. Возможности логирования в wine
Я пошел на оф. сайт wine и начал искать информацию о том, как заставить wine выдать мне сообщение обо всех ошибках, которые происходят при запуске приложения. Вся информация расположена по адресу www.winehq.org/developer-cheatsheet. Хочется сказать, что возможности логирования у wine очень богатые, и позволяют получать разнообразную информацию о запускаемой программе (можно даже проследить цепочку вызовов всех WinAPI-шных функций со всеми входными и выходными параметрами).
Итак, запускаем wine и снимаем логи:
Сначала я запустил без параметров: wine king > wine.dbg 2>&1
. На выходе получил
fixme:win:EnumDisplayDevicesW ((null),0,0x32f110,0x00000000), stub!
fixme:win:EnumDisplayDevicesW ((null),0,0x32f424,0x00000000), stub!
Здесь ничего интересного нет.
Небольшое отступление: описание подсистемы логирования в wine
- В wine есть несколько классов (хотя мне привычней слово уровень) логирования — это FIXME, ERR, WARN, TRACE, MESSAGE. Они отвечают за важность сообщения и его тип
- Также в wine есть отладочные каналы (debugging channels), они отвечают за ту или иную подсистему внутри wine. Например, reg отвечает за подсистему реестра и т.д
- Контроль логирования осуществляется с помощью переменной окружения WINEDEBUG. Например, я запускал со следующими аргументами: WINEDEBUG=warn+all,trace+all
Итак, возвращаемся к нашей задаче. После изучения возможностей логирования я начал запускать игру с разными параметрами логгирования, остановился на указанном выше.
После запуска я получил лог на 40 мегабайт. Понятно, что вручную в нем найти что-то нереально. Сначала я отфильтровал его по классу ERR, но не нашел там ничего интересного. Затем я решил вернуться к сообщению из warn.log, и отфильтровал его по SetCoop:
cat wine.dbg | grep SetCoop
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x15ae78)->(0x40066,0000105b)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x15ae78) DDSCL_NORMAL is not compative with DDSCL_FULLSCREEN or DDSCL_EXCLUSIVE
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x15ae78)->(0x40066,00000008)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel SetCooperativeLevel retuning DD_OK
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x15ae78)->(0x40066,00000008)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel SetCooperativeLevel retuning DD_OK
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x15ae78)->((nil),00000008)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel SetCooperativeLevel retuning DD_OK
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x160420)->(0x40066,0000105b)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x160420) DDSCL_NORMAL is not compative with DDSCL_FULLSCREEN or DDSCL_EXCLUSIVE
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x160420)->(0x40066,00000008)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel SetCooperativeLevel retuning DD_OK
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel (0x160420)->((nil),00000008)
0009:trace:ddraw:IDirectDrawImpl_SetCooperativeLevel SetCooperativeLevel retuning DD_OK
Здесь уже более интересные данные. Мы видим, что в функцию SetCooperativeLevel были переданы несовместные параметры, это уже что-то. Если посмотреть в логе предыдущие строки, то мы увидим и все параметры, которые передаются в фунцию:0009:trace:ddraw:DDRAW_dump_cooperativelevel - DDSCL_FULLSCREEN DDSCL_ALLOWREBOOT DDSCL_NORMAL DDSCL_ALLOWMODEX DDSCL_EXCLUSIVE
Описание всех параметров можно найти в MSDN
Анализ исходного кода
Далее я перешел к анализу исходного кода и документации на функцию SetCooperativeLevel.
Найти нужную функцию в wine несложно, она находится в %SRC_ROOT%/dlls/ddraw/ddraw.c. Функция называется IDirectDrawImpl_SetCooperativeLevel.
Открываем MSDN и находим описание на функцию SetCooperativeLevel и видим:
DDSCL_NORMAL
The application functions as a typical Windows application. This flag cannot be used with the DDSCL_ALLOWMODEX, DDSCL_EXCLUSIVE, or DDSCL_FULLSCREEN flags
То есть это означает что данные флаги не могут быть вместе.
Надо отметить, что реализация wine полностью соответствует документации и выдает код ошибки DDERR_INVALIDPARAMS в данном случае. Я решил для теста попробовать убрать проверку на конкретную комбинацию флагов:
/* DDSCL_NORMAL or DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE */ if(cooplevel & DDSCL_NORMAL) { /* Can't coexist with fullscreen or exclusive */ /** @note commented for test only if(cooplevel & (DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE) ) { TRACE("(%p) DDSCL_NORMAL is not compative with DDSCL_FULLSCREEN or DDSCL_EXCLUSIVEn", This); LeaveCriticalSection(&ddraw_cs); return DDERR_INVALIDPARAMS; } */
Компилируем и инсталлируем wine, запускаем Дальнобойщиков и…
Загружается знакомое меню игры, победа! Пробуем создать игру, игра создается и я могу покататься на своем любимом грузовике Зил!
Доработки по результатам исследования
Вдоволь накатавшись по виртуальной карте я решил, что игра работает достаточно стабильно и значит, пришло время оформить наши результаты в виде патча и создать заявку в багтрекере wine.
Для того чтобы полностью разобраться с данной функцией я решил написать тестовую программу для win32, которая бы выдавала код ошибки в зависимости от комбинации флагов DDSCL_* и я увидел, что на конкретной комбинации флагов (которую используют «Дальнобойщики») функция SetCooperativeLevel возвращает DD_OK, то есть успех. Также стоит заметить, что данная функция возвращает DD_OK гораздо чаще, чем это описано в MSDN. Вполне возможно, разработчики игры не учли различие в описании и в реализации данной функции и использовали флаги, которые запрещены в документации.
Я создал заявку, приложил к ней лог и патч.
На самом деле патч очень простой, я просто закомментировал проверку на запрещенные флаги (как в примере выше), однако данная функция используется во многих приложениях, поэтому необходимо провести более детальный анализ. Для этого я приложил к заявке программу для win32, которая перебирает все возможные комбинации флагов и выдает соответствующие коды ошибок.
Автор: prograholic