Казаки снова война для линукс

Реверс-инжиниринг «Казаков», часть последняя: второе дыхание

После нескольких месяцев работы над исходным кодом игры «Казаки: Снова война» я наконец-то могу умыть руки и представить результат своих трудов. В этой статье мне хотелось бы поделиться с вами опытом рефакторинга этого незаурядного проекта, в частности кодовыми курьёзами. Всем любителям некро-программирования посвящается…

Начало

Первое и одно из самых неприятных препятствий было не в коде, а в самих проектах. Всего их четыре:

  • статическая библиотека CommCore.lib (сетевой протокол GSCp на базе UDP)
  • динамическая библиотека IntExplorer.dll (игровое лобби на сервере)
  • динамическая библиотека IChat.dll (чат в игровом лобби)
  • исполняемый файл dmcr.exe

И эти ребята повидали некоторого кода! Из-за круговой зависимости между проектами IChat.dll и dmcr.exe для линковки требуется хотя бы один lib-файл из предыдущей сборки. Сами файлы проектов неверно конвертируются в Visual Studio 2015: они содержат ссылки к библиотекам, которые не отображаются в свойствах проекта, абсолютные пути к файлам в системе одного из разработчиков и прочие сюрпризы. В конце концов мне надоели танцы с бубнами, и я создал все проекты заново, попутно узнав, что в архиве присутствуют лишние файлы с устаревшим исходным кодом, который при включении в проект приводит к конфликтам. Ну и куда уж тут без особенностей линковки, для которой обязательно нужно исключить libc.lib.

Стартуем

Так, с проектами разобрались, теперь можно браться за дело. Компилятор рад за нас и приветствует множеством ошибок C2065: необъявленный идентификатор. Смотрим код и видим повсюду такую картину:

Конечно, можно было бы выставить /Zc:forScope- и забыть об этом, но мы же не кочегары и не плотники. Правим ручками больше сотни таких отрезков кода, продолжаем.

Следующее препятствие заключалось в графическом элементе, точнее в DirectDraw 7. Он активно использует механизм замены системной палитры. И если ранее это было повсеместной практикой, то начиная с Windows Vista такие фокусы больше не проходят. Дело в том, что DWM вместе с Windows Aero вплотную работают с палитрой и не терпят конкуренции. В итоге множество старых игр страдают от искажения цветов.

Не являясь экспертом по DirectX, я стал искать готовое решение и нашёл его в версии «Казаков», опубликованной на Steam в 2010 году. Помимо самой библиотеки ddraw.dll в папке с игрой присутствует дополнительная библиотека mdraw.dll, которая экспортирует функцию инициализации DirectDrawCreate(). Скажу честно – я не знаю, что именно ребята из GSC написали в их библиотеке DDemu DirectDraw Emulator в 2008 году, но она прекрасно справляется со своей задачей. Недолго думая я добавил соответствующую обёртку в Ddini.cpp и забыл об этой проблеме.

Затем встал вопрос об отладке полноэкранного приложения. Здесь мне снова повезло – в коде был предусмотрен отладочный режим, в котором игра запускалась в углу экрана в безрамном окне с фиксированным размером. Мне требовалось лишь довести его до ума, добавить смену разрешения, обработать захват и возврат курсора в зависимости от того, в меню ли игрок или в активной игре и добавить соответствующие параметры при старте. Теперь можно было удобно запускать игру в отладчике с ключом /window.

Весёлая арифметика

Одной из моих целей было добавление настроек для многопользовательских игр, например, возможность отключать дипломатический центр и рынок или ограничивать доступные корабли в верфи. Расширив интерфейс игровой комнаты и добавив нужные ветки в коде, я увеличил массив PlayerInfo.UserParam[], в котором хранятся эти настройки, с семи до десяти элементов. Вот только протестировать новые опции никак не получалось — при старте игры ИИ начинал распоряжаться моими крестьянами вместо своих и играть за меня, при этом его крестьяне стояли неподвижно. Весело, но так не пойдёт.

Причина такого поведения ИИ крылась в следующем финте ушами при копировании настроек от хоста игры в буфер обмена:

А вот так декларирована структура PlayerInfo:

Как видим, по смещению MapName + 60 находится COMPINFO[8]. Соответственно, при увеличении массива UserParam[7] вызов memcpy() промахивается, и в буфер попадают неверные данные о том, за каких игроков должен играть ИИ. Проблема решается заменой офсетной математики на прямое обращение по адресу PINFO[HostID].COMPINFO.

В итоге я всё же принял решение не трогать UserParam[], а добавить массив UserParam2[3] в конце структуры, так как в одном из последних элементов хранится версия клиента и любое изменение структуры до него чревато неверным определением версий в игровом лобби. А так игроки с версией 1.35 будут видеть, что у других обновлённая версия игры.

Читайте также:  Xerox workcentre 3025 scan driver windows 10

Какие уроки можно почерпнуть для себя из этого?

  • В структуре, передающейся по сети, первым элементом должна быть версия клиента.
  • Никогда не исходить из того, что расположение структуры в памяти будет неизменным.
  • Писать функции сериализации, а не полагаться на #pragma pack(1) и побайтовое копирование.

Невидимый Джо Дефайн

Разбираясь с механикой отображения внутриигровых текстовых сообщений с целью увеличения времени отображения и максимального количества сообщений на экране, я наткнулся на занимательную константу:

«Ну и что в этом такого? — спросите вы — Всего лишь пробел в качестве константы». Ну, во-первых, пробел был бы крайне странным выбором для идентификации чего-либо в строке текста, а во-вторых это вовсе не пробел. SIGNBYTE определён как 0x7F, или управляющий символ DEL. И если ваш браузер достаточно осмотрителен и хотя бы показывает, что между кавычками что-то есть, то Visual Studio 2015 вероломно рисует », между которыми курсор «спотыкается» на один символ.

Пожалуйста, если уж вы используете непечатаемые символы, то указывайте их в коде через шестнадцатеричное значение, а не как символ.

Правовой аспект

Всякий раз удивляюсь, когда для запуска игры требуются права администратора. И всякий раз думаю что-то вроде «ну как так можно игры программировать-то». Но в этот раз у меня и код на руках был, и собирал его я сам, а окно UAC всё так же не давало мне покоя.

Ответ нашёлся совершенно случайно, когда я подумал, что неплохо было бы вписать в свойства исполняемого файла информацию о том, что эта версия игры не оригинальная и не поддерживается разработчиками. Манифеста в проекте, естественно, не было, но был файл ресурсов Script1.rc. Каково же было моё удивление, когда после изменения блока VS_VERSION_INFO игре перестали требоваться права администратора!

Оказывается, ОС Windows начиная с Windows Vista применяет эвристический алгоритм для определения приложений, которым может потребоваться повышение привилегий. Называется эта функция «Технологией обнаружения установщика» (см. статью в ИТ-центре Windows), и обычно она реагирует на ключевые слова вроде install или setup. Но в нашем случае виновником оказался параметр CompanyName — если он содержит строку «-GSC-\0», то просыпается UAC и требует прав администратора.

Как уберечь своё приложение от такой эвристики со стороны Майкрософт, существующей и грядущей? А никак. Сегодня вы разрабатываете игры, а завтра уже стоите в одном ряду с Inno Setup и InstallShield.

Партизанский sscanf()

На этот баг я наткнулся, когда после добавления дополнительных настроек игры при проигрывании записей прошедших игр возникала ошибка синхронизации, т.е. запись не шла по тому руслу, по которому развивалась игра. При этом ошибка появлялась, по всей видимости, случайно и я долго не мог найти причину.

Опущу скучные детали отладки и перейду к самому соку. Настройки игры, вид карты и данные о выбранных нациях передаются через имя файла случайной карты, которое имеет формат , где

Загвоздка здесь в том, что для четвёртой переменной указан тип %x, в то время как диапазон символов в ней выходит за рамки шестнадцатиричной системы и простирается до буквы K. Если в игре присутствуют игроки, которые выбрали нации с индексом выше F, то sprintf() преждевременно закончит парсинг и вернёт 4. Параметры не будут интерпретированы, у ИИ будет неправильная информация об игре и он будет принимать другие решения, что приведёт к рассинхронизации.

В дополнение к этому идёт тот факт, что sprintf() вызывается исключительно для ADD_PARAM — остальные переменные нигде не используются. Решение проблемы относительно простое:

Флаг * указывает функции, что значение не следует сохранять в переменной. Кстати, пoсмотреть, каким образом я реализовал кодирование 10 игровых настроек на месте тех же 7 цифр можно здесь. «Зачем?» — спросите вы. А потому что менять длину строки с именем файла карты по своему усмотрению показалось мне не очень хорошей идеей (см. выше в «Весёлой арифметике»).

Самое интересное для меня здесь это тот факт, что баг проявил себя лишь при компиляции в . Получается, что реализация функции sscanf() в стандартной библиотеке за прошедшие годы стала построже и впредь не будет прощать такие вольности со стороны программистов.

Памятка: Следовать в первую очередь требованиям документации, а не принципу «рабочий код — правильный код».

Тонкости языка

Локализация это отдельная тема для любого разработчика, но такое я увидел впервые:

Читайте также:  После установки windows 10 нет обновлений

Если вам этого мало, предлагаю заглянуть под спойлер и посмотреть, для чего же нужен этот «крипто-ключ».

Вот таким нехитрым образом можно запороть локализацию на этапе компилирования. Если, например, «английскому» dmcr.exe подсунуть архив с ресурсами из русской версии, то всё, что останется от игры — окно ошибки access violation. Потому что ни до, ни после «isi memory decryption» содержимое буфера не проверяется. А вот если мы распакуем архив all.gsc, заменим файлы и запакуем его обратно, то в игре нас будет ждать русский интерфейс.

Посмотрев на эту XOR-вакханалию я решил ограничиться английской версией, но с поддержкой кириллицы в чате. Так как весь текст отрисовывается через собственные шрифты игры, я скопировал из русской версии ресурс mainfont.gp. Осталось только отловить символы, выходящие за пределы диапазона ascii, и правильно сопоставить коды букв с «индексом кадров» этого файла (формат GP используется в игре повсеместно для хранения графики, в том числе и для анимации). Не самое элегантное решение, зато работает безотказно, причём на сервере в чате с игроками под версией 1.35 тоже.

UDP без дырок

К сожалению, в оригинальных «Казаках» не был реализован механизм UDP hole punching, который позволил бы игрокам подключаться к игровым комнатам, даже если их хост находится по ту сторону NAT своего провайдера.

К счастью, товарищ, известный под ником [-RUS-]AlliGator, запустивший и поддерживающий сервер cossacks-server.net, выделил немного своего времени и мы договорились о дополнительном протоколе, по которому хост игры будет поддерживать UDP соединение с сервером, и по которому сервер сможет сообщать внешний UDP порт хоста игрокам, желающим к нему подключиться.

Все детали реализации можно посмотреть в классе UdpHolePuncher. Соединение инициализируется при создании игровой комнаты хостом, после чего он вплоть до старта игры поддерживает связь, отправляя небольшие пакеты. Это нужно, т.к. NAT может при каждом новом UDP соединении присваивать другой внешний порт, а так сервер наверняка знает, что в данный момент времени хост доступен из-за NAT по тому порту, с которого приходят пакеты.

Соответствующие изменения были внесены и в процедуру обработки команд сервера и в структуру RoomInfo в библиотеке IChat.dll. Поддерживаются следующие дополнительные переменные при создании игровой комнаты:

  • %PROF: идентификатор игрока. С помощью него сервер сможет различать хостов
  • %CG_HOLEHOST: адрес сервера, обрабатывающего UDP пакеты
  • %CG_HOLEPORT: порт сервера, на котором слушается UDP
  • %CG_HOLEINT: интервал, с которым клиент должен отправлять пакеты

Получив эти данные, хост игры открывает дополнительное соединение и поддерживает его. Желающие присоединиться получают дополнительную переменную %CG_PORT при подключении к комнате. И только если её нет, используется константа DATA_PORT.

Хотя весь этот механизм уже имплементирован в клиенте игры, протестировать мне его ещё не удалось, т.к. контакт с Аллигатором оборвался. Незадолго до этого он выложил в открытый доступ исходный код своего сервера, — спасибо тебе за это! — так что если кто-то готов поднять эстафетную палочку, я буду только за.

Послесловие

Статья и так уже вышла длиннее, чем я планировал, так что буду краток. Хотя этот проект занял продолжительное время и ощутимо истощил запас моего энтузиазма к реверс-инжинирингу и анализу исходного кода, я рад, что взялся за него. Рад, что написал тогда статью на Хабр с описанием своего первого, ассемблерного, костыля для «Казаков». Рад, что в комментарии пришёл Максим fsou11 и выложил в свободный доступ исходный код игры. Также я благодарен сообществу LCN за ценные советы, объяснения и помощь в тестировании.

Источник

Казаки снова война для линукс

Cossacks: Back to War 1.42

This repo contains the results of a severe refactoring and bugfixing of the original source code from Cossacks: Back to War 1.35 released back in 2002. The solution is meant to be build in Visual Studio 2015 on a Windows 10 system.

List of the most important bugs and crash causes that were fixed:

  • Unreasonable high game speed on modern CPUs
  • Messed up color palette
  • Crashing when there are too many units on screen
  • Crashing when building very long walls
  • Crashing when selecting too much units at once
  • Multiple building explosions possible while performing upgrades
  • Instant building explosion upon repeated DEL order while performing an upgrade
  • DEL key killing selected units while entering a number in the resource transfer dialog
  • Buggy production queue management in multiple buildings at once
  • Grenadiers wouldn’t shoot on attack move when in formation
  • Way too fast mouse scrolling
  • Crashing upon exiting a game
Читайте также:  Как активировать windows 10 корпоративная ltsc

Main gameplay changes:

  • Stone mining upgrades now complement each other instead of overlapping
  • New option: Market (no trade; independent trade courses)
  • New option: Diplomatic Centre (no mercenaries allowed)
  • New option: Shipyard (no ships; fishing boats only; fishing boats and ferries; 17th century only)
  • Addition to option: No artillery at all
  • Game speed is set in the lobby and stays constant during the game
  • Production queue multiplicators: Shift (5), Alt (20), Tab (50), F1 (15), F2 (36) and F5 (250)
  • «Ready» flags are removed when the host changes settings in the lobby
  • «Click-guessing» the enemy’s position through a fog of war bug is fixed away
  • No hills on «Plain land» maps anymore
  • Windowed modes (activated through command line arguments /window and /borderless)
  • Default nation in lobby is set to «Random»
  • Minimap visibility improved through contrastful colors
  • Better handling of Ctrl+5 key events
  • Game starts without UAC prompt
  • Menu resolution is fixed at original 1024×768, no more stretching
  • Upon clicking «Ready» in the lobby a list of all selected options is displayed
  • Screen resolutions fitting to your aspect ratio are marked with an asterisk
  • Confirm prompt added for surrendering and game exiting
  • Correct handling of DPI scaling settings
  • Upgrade research is canceled upon building capture
  • Fixed doubleclick «select all peasants on screen» bug while queueing buildings
  • Greatly increased the credits screen delay
  • Maximal screen resolution width is set to 1920x to mitigate impact of 4K displays
  • Removed the nagging «Unit / building xxx prevents you from shooting» messages
  • Increased the total amount and the lifetime of ingame chat messages from other players
  • Game recording is always on

You could stumble upon an archive somewhere on the net… If you do, those could be helpful:

File Size SHA-1
Cossacks142.zip 310,729,118 5b0dc863549db8a9d832c915dbc0d240681e82f3
dmcr.exe 1,975,296 3bb7d0c722d232b08033fa78ad5d4aefe3fc8900
IChat.dll 108,544 bcd17e3fe3fc242dfec5ec89497ba5863fe40729
IntExplorer.dll 114,176 310c721dfd259dd49c5905694981e9ebd78aa43c
resources.gsc 243,724,485 7522480fd6ccbffc1e5298b29fb7b12b5a54d9a0

KaesDingeling kindly uploaded a German translation patch. Place the extracted override.gsc file in your game folder to activate it.

See #19 for details on how to create your own translations.

Казаки: Снова Война 1.42

Здесь представлены результаты усердного рефакторинга и исправления исходного кода игры «Казаки: Снова Война» 1.35, вышедшей в 2002 году. Решение следует собирать в Visual Studio 2015 на системе Windows 10.

Самые важные из исправленных ошибок и вылетов:

  • Слишком высокая скорость игры на современных процессорах
  • Искажение цветовой палитры
  • Вылет при большом количестве юнитов на экране
  • Вылет при постройке длинных стен
  • Вылет при выборе множества юнитов
  • Многократный взрыв здания во время апгрейда
  • Моментальный взрыв здания при нажатии DEL во время апгрейда
  • Удаление юнитов при нажатии DEL в окне передачи ресурсов
  • Невозможность изменять очередь юнитов в нескольких зданиях сразу
  • Не стреляющие гренадёры в формациях
  • Слишком быстрый скроллинг
  • Вылет при выходе из игры

Основные изменения в геймплее:

  • Апгрейды на добычу камня дополняют друг друга согласно описанию
  • Новая опция: рынок (без обменов, независимые курсы)
  • Новая опция: дип. центр (игра без дип. центра)
  • Новая опция: верфь (без кораблей, только рыболовные, только рыболовные и транспорт, только корабли 17 века)
  • Дополнительная опция: совсем без артиллерии
  • Скорость игры настраивается в лобби и остаётся неизменной
  • Модификаторы очередей: Shift (5), Alt (20), Tab (50), F1 (15), F2 (36) and F5 (250)
  • Изменение настроек в лобби сбрасывает флаги готовности
  • Исправлен баг, позволяющий прокликивать сквозь туман войны
  • Убраны холмы из генерации ландшафта типа «равнины»
  • Режим окна и безрамного окна (ключи /window и /borderless)
  • Стандартная нация «случайная»
  • Контрастная миникарта
  • Исправлена обработка горячих клавиш Ctrl+7
  • Не требуются права администратора
  • Фиксированное разрешение главного меню
  • После нажатия «готов» показывается список всех настроек игры
  • Подходящие разрешения экрана помечаются звёздочкой
  • Диалог подтверждения перед тем, как сдаться или выйти в меню
  • Правильная работа игры с включённым DPI scaling
  • При захвате здания отменяется исследование апгрейда
  • При строительстве зданий двойной щелчок больше не выбирает всех крестьян
  • Задержка перед включением титров значительно увеличена
  • Разрешение экрана ограничено шириной в 1920px
  • Убрано сообщение «[юнит|здание] мешает выстрелу»
  • Увеличено количество и время показа сообщений от других игроков
  • Запись игры всегда включена

Если вы вдруг наткнётесь на какой-то архив на просторах сети, то вам может понадобиться это:

Источник

Оцените статью