Pull to refresh

Arcanum и Fallout 2 с сенсорным экраном и стилусом в Windows 8

Reading time7 min
Views59K
Как-то так вышло, что завелся у меня Windows 8 планшет. Не очень удачная модель — громоздкий как помощник, слабоватый как рабочая станция, но зато с стилусом, а самое важное, с 32-битной Win32 системой. Имея некое количество старых игр с распродаж GOG и Steam, я планировал как-нибудь засесть с этой таблеткой и переиграть все, что можно и нельзя. Но вот как-то не хватало времени и настроения, да еще и пробные запуски показали, что играть надо с мышью — курсор от тач-скрина убегал неизвестно куда, да и правый клик долгим нажатием был бы откровенно неудобным. Планшет пылился пол-года в углу до недавней раздачи Fallout от GOG и эта раздача подтолкнула меня к действию. Начинался вечер пятницы, планшет обзавелся USB мышью, а я устроился поудобнее на диване и начал по списку с самого верха — с Arcanum.
Через пол-часа создания персонажа (а ведь это дело очень важное и ответственное!) левая рука с трудом держала устройство, а запястье правой подозрительно начало тянуть, намекая о туннельном синдроме и прочих радостях неудобного хвата. Вспомнив нехорошим словом разработчиков некой эргономичной складывающейся мыши, я полез искать драйвера, патчи или еще хоть что-то, чтобы играть с тачскрина или хотя бы стилуса.
Патчей не нашлось. Единственный похожий драйвер был платным и без пробного режима. Вот в этот момент в голову и пришла идиотская (это я теперь понимаю!) мысль — «там ведь наверняка приходит какой-то WM_TOUCH и неправильно преобразуется в WM_MOUSEMOVE»… Забегая вперед, Арканум теперь у меня полностью управляется с тачскрина, правда, выходные закончились и больше хочется спать, чем играть.

Для нетерпеливых
Проект доступен на github, там есть исходники и бинарник. Это DLL файл, который надо использовать как лаунчер для игры через rundll32. Нажатие двумя пальцами делает правый клик, нажатие тремя пальцами имитирует клавишу ESC — для Arcanum этого достаточно. Fallout 2 поддерживается в бета-режиме, инструкция там же, на github. Правое нажатие там не работает, курсор изредка смещается, но это можно исправить затащив его в левый нижний угол экрана.


Visual Studio 6.0


Как-то вышло, что хуками, патчами и инжектами под Win32 я болел дюжину лет назад и потом с огромным удовольствием закрыл эту страницу в жизни. И вот сейчас, мечтая «пол-часа покодить, а потом поиграть» я достал старый винчестер, списал оттуда пару проектов и любимый инструмент — VS98, он же Visual Studio 6.0. Да, я постоянно пользуюсь VS 2012 для W8 и WP мобильных программ, но, каюсь, и понятия не имею, на что сейчас в ней похож Platform SDK и не стало ли там модным/обязательным что-то вроде кодинга низкоуровневых вещей на C# или C++/CLI. А тут еще и оказалось, что VS98 при своем весе в 120 мегабайт заодно и отлично работает под wine на моей рабочей машине.
Очистка старых проектов от жести вроде подмены IAT затянулась, но уже через пару часов я внедрял свой модуль в игру и мог отслеживать его поток сообщений (сабклассинг через SetWindowLong и бла-бла-бла). Spy++ по понятным причинам с полноэкранной игрой работать не хотел, потому я писал логи и потом изучал все, похожее на мышь и тач. На этом вечер пятницы закончился и продолжение я отложил на утро.

WM_TOUCH


Утром я пробежал глазами пару статей про работу с тачскрином и сел отлавливать WM_TOUCH. Отличное сообщение, приходит регулярно, правда, содержит в одном блоке по десятку-другому нажатий. Интересно было то, что координаты в игру приходили корректные — они точно соответствовали месту нажатия на экране. Это казалось не принципиально, потому я стал генерировать WM_MOUSEMOVE при каждом событии. Результат удивил — ничего не изменилось. Вообще ничего. Заменил WM_MOUSEMOVE на SetCursorPos и таки увидел результат — уползание курсора стало заметно менее хаотичным. Казалось, что дело в шляпе — надо лишь понять, что происходит не так и почему курсор каждый раз смещается в сторону… Лишь когда на улице стемнело, я понял, что что-то пошло не так :) Все логичные и не-логичные методы, формулы, корректировки координат, колдовство с mouse_event, SendInput, отмена тех или иных событий — ни в одном случае я не смог заставить курсор перемещаться к месту нажатия. Это казалось бредом или мистикой, но где-то внутри игры была собственная пара координат, которая изменялась не понятным мне способом и не управлялась напрямую.

DirectInput и все-все-все


В воскресенье я проснулся демотивированный, но все еще с надежной. Сегодня я хотел попробовать повторить все то, что идет от реальной мыши, но с данными тачскрина. Я подключил грызуна и начал отслеживать его действия и тут обнаружилось, что действия мыши вообще не проходят через мою оконную процедуру. Обзывая себя ослом, не знающим реалий игр, я укопался в гугл и обнаружил, то, что сбивало меня с толку: часто игры получают информацию о мыши либо через DirectInput, либо в raw виде, минуя Window Message Queue. События тачскрина и стилуса как раз являются исключением — они проходят через очередь сообщений и где-то в недрах DefWindowProc превращаются в действия с мышью. Соответственно, WM_MOUSEMOVE и иже с ним, могут вообще отсутствовать (а для мыши так и происходит) и попытка их отправки, подмены и пр. на игру никак не влияют. Буквально, две трети моих экспериментов просто игнорировались игрой, а оставшиеся конфликтовали с DefWindowProc. Более того, WM_TOUCH посылался только для совместимости с Windows 7 программами, а основными тач-событиями в Windows 8 уже считаются WM_POINTER*.

Случайность или промысел?


На этом этапе я уже знал, что мне надо и видел свет в конце туннеля — я не дам вообще DefWindowProc обрабатывать события тачскрина и буду все делать сам. Эмоциональный подъем сделал свое дело и в какой-то момент неожиданно тачскрин начал ставить курсор точно в место нажатия. Это было странно, ведь модуль перехвата еще не был готов. Поиск дихотомией нужного участка (отключаем половину программы и смотрим на поведение, затем либо возвращаем назад, либо отключаем половину оставшегося кода) показал, что я одновременно вызвал
RegisterTouchWindow(hWnd, TWF_WANTPALM)
и
SetProp(hWnd, "MicrosoftTabletPenServiceProperty", (HANDLE)1);

* Не смотря на идиотский вид и смысл последней команды, она вполне корректна и документирована, хоть и для Tablet PC 2003го года.
Вызов этих двух методов приводит к тому, что в WM_TOUCH мы получаем только одни координаты за раз, а внутренности DefWindowProc генерируют относительно корректные координаты. А дело-то в том, что обычно WM_TOUCH не посылается сразу при нажатии, а накапливает данные для фичи «press and hold». Что характерно, коллеги уже когда-то сталкивались с похожей ситуацией:
At the end of four days of futility, I gave up and did two things. First, I posted a message on the MSDN forums asking «what the fuck», although I did not actually use the word «fuck» in the post because I thought it might trigger an automatic rejection.
(...)
Thankfully, a few days later, to my surprise, someone actually answered my forum post. In it, they referred me to a technical article written in 2003 that showed how to do exactly what I wanted: instead of messing with the TabletPC API at all, you just set a secret global text atom on your window, and poof! TabletPC disables press and hold for your window.
(...)
So somehow, in all their COMness, with multiple libraries and hundreds of GUIDs and pages and pages of class documentation, the TabletPC SDK had failed to include a define for, or even a mention of the existence of, this special atom. Or what «press and hold» was (since it would have been really helpful to know that term for searching before I started — I might have been able to find the secret technical article that way).

Это еще было не полное решение, но все-равно огромный прогресс! Собственно, именно в таком режиме стал играбельным Fallout 2, хотя и изредка теряет совпадение виртуальных и реальных координат — курсор смещается в сторону. Решается это прямо в игре, перетаскиванием курсора в место, где он больше не может двигаться.

Полноценная реализация


Дальше уже все было делом техники — когда DefWindowProc перестал мешать и WM_TOUCH/WP_POINTER перестали склеиваться, я стал их обрабатывать и слать события через mouse_event; поведение курсора стало таки прогнозируемым. Единственное, что не получилось решить — единовременная работа мыши и тачскрина. Мышь не генерирует вообще WM_ сообщений, потому мы не можем узнать ее точные координаты, а GetCursorPos дает только то, что мы сами туда запишем. После перехвата событий тачскрина значения GetCursorPos вообще перестали меняться, хотя курсор двигался мышью и тачем. Для Arcanum еще критична правая кнопка, потому ее повесил на нажатие двумя пальцами. Для выхода в меню и сохранения нужна ESC, она прижилась на жесте 3 и больше пальцами. На этом я решил сегодня и остановиться, вычистил код, залил на github и… вместо того, чтобы поиграть, сел писать статью на Хабр ;)

Для скептиков и ностальгирующих

Тачскрином был выбран спелл, использован, а затем правым «кликом» из режима каста вышли
Вступительные ролики пропущены тапом, персонаж создан и инвентарь открыт тоже тачскрином (на «чистой» версии это не возможно)



Баги и недоработки


  • Уже пару раз писал, но продублирую: если пошевелить мышью, то курсор собьется, надо играть только с тача. Курсор можно заганть в угол экрана и потом вытащить оттуда тачем, это воостанавливает синхронизацию координат
  • В Fallout 2 перехват WindowProc через какое-то время крашит игру. В следующих версиях разберусь, а пока можно играть и без него: в проекте сделано две точки входа — с перехватом WM сообщений и без него. К сожалению, кнопочки в игре мелковаты и нажимать пальцем их не удобно, стилусом выходит получше.
  • Хук на создание новых окон работает несколько секунд, пока запускается игра. Если в это время запустится какая-то еще программа, то DLL будет подгружена в ее адресное пространство. Как результат, ее нельзя будет удалить до ребута. Не критично, а DLL можно и переименовать. В ходе отладки у меня скопилось сотни полторы таких переименованных.
  • Windows 8 богата убогимимодными жестами, некоторые из которых мешают играть. Их можно выключить программно, но для этого нужно компилировать в чем-то новее, чем VS 6.0. А пока можно отключить эти жесты в реестре:
    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell\EdgeUI]
    "DisabledEdges"=dword:0000000f
    — реализовано
  • Код написан бородатым динозавром в IDE 98-го года. Ну уж звиняйте, панове. Он еще и крашиться может, вы ведь в курсе?
  • DLL вышла крупноватой (48кб), но все-равно на нее могут делать стойку некоторые антивирусы — там ведь инжекты, глобальные хуки и вообще вмешательство в работу системы!


Поддержка


Все желающие выразить свою благодарность могут не заморачиваться и сказать «спасибо». Можно даже вслух и перед монитором, не обязательно писать об этом. Желающим назвать меня некрофилом или как-то еще, советую тоже произнести это несколько раз вслух и не увеличивать энтропию мирового интернета. Проекту заодно не помешает тестирование на большем количестве старых игр, решение проблем с Fallout, апгрейд до более новой IDE, фиксы, вики-страница и пр. Я не против и патчей под конкретные игры. Форкайте, делайте, потом смержим.

UPD: В обеденный перерыв обновил проект до VS 2012, добавил авто-отключение Windows 8 charms, поддержку кнопки на стилусе. Заодно попробовал работать с ASI Loader, но что-то не сложилось. Также был отловлен странный краш при запуске и сделан для него хако-фикс.

UPD 2: Как оказалось, библиотека неверно работала, если стояла не-дефолтная скорость мыши или была включена «Расширенная точность указателя». Полного решения этой проблемы я пока не вижу, потому DLL при запуске выключает эти пункты, как не совместимые с сенсорным управлением. Если кому интересно, то «Расширенная точность» является чьей-то глупой шуткой, которая удваивает смещение мыши по любой оси, если его значение больше 6 пикселей и учетверяет, если больше 10. Т.е. сместив мышь на 7 пикселей влево и 12 вверх мы с этим режимом получим смещение на 14 пикселей влево и 48 вверх. Не вижу в этом никакой повышенной точности, причем, пределы 6 и 10 заложены где-то внутри системы уже десятилетие назад и не могут меняться пользователем.

Tags:
Hubs:
+79
Comments23

Articles

Change theme settings