как попасть в код игры
Почему никак не узнать исходный код игры?
Объясните пожалуйста, почему я никогда не узнаю исходный код какой-нибудь игры пока разработчики его сами не выложат в открытый доступ или хакеры его не сольют?
В обычных приложениях его можно узнать через DotPeek, но в большинстве игр нет.
Почему нельзя узнать исходный код, как например достать текстуры из игры?
Почему после компиляции всё?
Если это всё как-то шифруют, то как работает эта шифровка?
Я понимаю вопросы глупые, но объясните мне пожалуйста как дурачку. Я просто не могу этого понять. =(
(Объясните пожалуйста на простых словах, без википедии, я не понимаю что там написано)
Вот и как голодному студенту узнать рецепт? Да, продукт перед ним. Да, он может расковырять свою порцию, отсортировать кукурузки и нарезку, по форме кубиков узнать каким ножом резали, подсчитать в граммах каждый ингридиент, фигакнуть соус на центрифуге, просветить спектроанализатором на химические элементы, прикинуть примерно из чего оно могло состоять. Но на это уйдет уйма времени и средств.
Потому куда важнее дать пояснение чем просто посмотреть чужой код
Заглянем за кулисы разработки: подборка исходных кодов классических игр
Обожаю заглядывать за кулисы. Мне интересно, как делаются вещи. Мне кажется, что большинству людей это тоже интересно.
Исторически так сложилось, что видеоигры не делятся исходниками. Конечно, они ведь предназначены для игроков. Но для программистов там всегда есть, на что посмотреть. И некоторые игры всё-таки выпускали свои исходники. А я давно намеревался сделать такую подборку.
К сожалению, почти все игры – для PC. Найти исходники для консолей или аркад почти нереально, и большинство программистов не в курсе различий подходов к программам на платформах, отличных от PC.
Многие игры после выпуска исходников были улучшены и дополнены сообществом – я намеренно даю ссылки только на оригинальные исходники. Так что, если вас вдруг интересуют апгрейды – они могут существовать.
Многие игры были рассмотрены на сайте Fabien Sanglard. Если вам интересны подробности их работы, то пожалуйте к нему.
Можно заметить, что многие игры принадлежат id Software/Apogee. Совпадение? Не думаю. id славится открытостью и привычкой выпускать исходники. Старые коммерческие игры уже не имеют ценности и были бы потеряны – не лучше ли, чтобы кто-то учился чему-то полезному на их основе?
Итак, приступим (в хронологическом порядке):
Colossal Cave Adventure (1976)
Разработчик: William Crowther and Don Woods
Издатель: Разные.
Платформа: PDP-10 и друзья.
“You are in a maze of twisty little passages, all alike.” («Вы находитесь в лабиринте из неотличимых друг от друга небольших извилистых проходов»).
Возможно, это не на 100% коммерческая игра. Но её продавали, кроме того, она имеет историческую важность. И, кстати, именно по мотивам названия этой игры все приключенческие игры называются adventure.
Оригинал был написан на Fortran, в котором современным программистам будет сложновато разобраться. Но последние версии были на C.
Catacomb (1989)
Разработчик: John Carmack
Издатель: Softdisk
Платформа: Apple II / DOS
Не путайте с Catacomb 3D. Это ранняя двумерная версия игры. Разработана Кармаком до создания id и полностью написана на Turbo Pascal.
Prince Of Persia (1989)
Разработчик: Jordan Mechner
Издатель: Brøderbund
Платформа: Apple II / DOS / many more
Обзор кода: fabiensanglard.net
Prince Of Persia произвёл фурор благодаря плавной анимации, голливудскому стилю подачи истории и интересному геймплею.
Написана полностью на ассемблере, что затрудняет задачу обзора кода. Рекомендую посмотреть интервью с Джорданом Мехнером, где он делится деталями о создании игры.
SimCity (1989)
Разработчик: Maxis
Издатель: Maxis / Brøderbund
Платформа: All
Игра начала новый жанр. В основе алгоритма – симуляция города посредством клеточных алгоритмов. Хороший пример кода, который стоит изучить для понимания принципов работы. Исходники для unix-порта 1990 года были выпущены в 2008 году.
Hovertank 3D / Catacomb 3D (1991)
Разработчик: id Software
Издатель: Softdisk
Платформа: DOS
Первая веха в истории трёхмерных шутеров id Software. Эти игры используют технику raycasting, которая была улучшена в следующем хите, Wolfenstein, где были добавлены текстуры.
Star Control II (1992)
Разработчик: Toys for Bob
Издатель: Accolade
Платформа: DOS / 3D0
Уникальная игра, не вписывающаяся ни в один из строгих жанров. Внешний вид чётко напоминает нам о 90-х годах и системе VGA, где цвета были подобраны не для красоты, а из стандартной палитры DPaint.
Рекомендую почитать обзор кода от The Escapist.
Исходник получен с порта на 3D0, оригинальный же был утерян. Это часто случается со старыми играми, когда разработчики уходят из компании.
Wolfenstein 3D / Blake Stone (1992/3)
На основе предыдущего движка Catacomb был сделан серьёзный апгрейд на VGA-графику. И играть стало интереснее. Как в большинстве случаев с компанией id, исходники сравнительно легко читать, хотя ключевые части написаны на 16-битном ассемблере (в Doom уже такого не встретишь).
Интересно отметить, что для рисования вертикальных линий они динамически генерируют разные функции для каждой из возможных высот стен.
У Fabien можно найти инструкцию по компиляции исходников на современных инструментах.
Blake Stone, ответвление от Apogee на том же движке, вышло в 1993 году, за неделю до Doom. Можно представить, почему оно кануло в лету.
Doom (1993)
В каком-то смысле это самый важный для изучения движок. В своё время это была революция – мир от первого лица, не плоский, как Wolf3D. Освещение, текстуры и изобретение DeathMatch.
Одной из самых знаковых вещей стало изобретение идеи «движка». До этого игры были сильно связаны с данными. Doom пропагандировал отвязку данных игры от движка. Это породило целые сообщества, модифицировавшие игры (Aliens TC, Fistful Of Doom).
Descent (1994)
Многие компании кинулись догонять Doom, запустив волну «Клонов Doom». Parallax удалось сделать нечто совсем другое.
В игре можно было летать на корабле по трёхмерному лабиринту из проходов, в отличие от 2.5D коридоров Doom (у id полная трёхмерность появилась лишь в Quake год спустя).
Gravity Force 2 (1994)
Разработчик: Jens Andersson and Jan Kronqvist
Издатель: Shareware
Платформа: Amiga
Многие современники вспомнят эту игру. Amiga Power однажды назвала её второй лучшей игрой всех времён.
Это не совсем коммерческая игра, она была выпущена по принципу платного shareware, а затем её раздавали бесплатно на диске Amiga Power. Включил её в список потому, что в ту пору вообще редкие игры выдавали свои исходники. Если вам интересно, как делались 16-битные игры, обратите внимание.
Heretic / Hexen (1994/5)
Это был уникальный клон Doom по двум причинам: 1) лицензированный движок Doom и 2) хороший геймплей
Заметные улучшения: возможность смотреть вверх и вниз, скриптовой движок для внутриигровых событий (новая идея на то время).
Rise Of The Triad: Dark War (1995)
ROTT это была странная игра. Она была порождена движком Wolfenstein 3D, при этом создатели умудрились эмулировать ощущения разных высот. Но всё равно игра не смогла конкурировать с Doom от 1993 года.
Marathon 2: Durandal (1995)
Разработчик: Bungie Software
Издатель: Bungie Software
Платформа: Apple Macintosh / Windows 95
Серия отличилась тем, что в своё время попала в крайне маленький список игр, доступных на Apple Macintosh. И, в общем-то, это клон Doom. А через 3 месяца после её выхода id Software выпустила знаменитый “qtest”, позволявший взглянуть на движок Quake.
Поскольку Маки тогда использовали лишь писатели с художниками, несмотря на все усилия, серия провалилась. Небольшая компания разработчиков имела неплохой успех на разных других платформах.
Duke Nukem 3D / Shadow Warrior (1996)
Разработчик: 3D Realms
Издатель: GT Interactive Software
Платформа: DOS
Code review: fabiensanglard.net
Из множества клонов, игры 3D Realms отличились хорошими попытками привнести нечто новое в игру. Движок Кена Сильвермана Build Engine добавил много интересных фич вроде наклонных полов, комнат, расположенных друг над другом и зеркал.
К сожалению, в отличие от самой игры, исходники просто ужасны. Я рылся в них несколько раз и до сих пор не могу разобраться, что там где. К счастью, обзор от Fabien проливает некоторый свет на происходящее в коде.
За дополнительной информацией обращайтесь на страницу автора.
Quake 1/2/3 (1996-1999)
Разработчик: id Software
Издатель: GT Interactive / Activision
Платформа: DOS / Windows / others
Code review: fabiensanglard.net (Quake 1)
Code review: fabiensanglard.net (Quake 3)
Тут писать особо нечего, вы и сами всё знаете. Знатная веха в создании полностью трёхмерных движков, без всяких хаков вроде 2.5D
Упомяну несколько интересных подробностей. Возможно, это первая коммерческая игра, скомпилированная компилятором с открытым исходным кодом (DJGPP for DOS, ранний порт gcc).
В игре был свой скриптовой язык “Quake C” (позже lcc у Quake 3). Он был встроен специально для того, чтобы игроки могли делать модификации. Это, вкупе с системой ресурсов PWAD, породило огромное сообщество моддеров.
В Quake 1 был инновационный механизм кэширования результатов шейдинга. Но после распространения 3D-ускорителей это потеряло смысл. Следующая игра от id, Rage, использовала эту же идею.
Кроме того, Quake был очень надёжным движком. Никаких глюков растра или обсчёта столкновений.
Abuse (1996)
Разработчик: Crack dot Com
Издатель: Electronic Arts / Origin Systems
Платформа: DOS / Linux / Mac
В игре было использовано несколько инноваций. Крутая система управления одновременно с мыши и клавиатуры. Динамическое освещение (неслыханная вещь для платформеров).
Но больше всего, как программисту, мне понравилась система «визуального Lisp». Вся игра заскриптована на языке, напоминающем Lisp. Поведение врагов можно изменять во время выполнения игры, а не просто включать в код.
Ещё одним интересным моментом стал способ, по которому события можно подключать во встроенном редакторе карт – визуально перетаскивать линии от выключателя к двери, или от ловушки к месту, где появляются враги. Присутствует возможность задавать логику И/ИЛИ в виде скрытых объектов на уровне. Такого я в других редакторах не встречал.
Коммерческого успеха игра не снискала и через два года исходники были опубликованы. Следующая игра от Crack Dot Com, Golgotha, была выпущена по принципу open-source, включая всю графику.
Aliens versus Predator (1999)
Какое облегчение видеть здесь шутер не за авторством id. И хотя технических инноваций в игре особо не было, сингл-кампания у неё вышла просто потрясающей. И игра остаётся хорошим примером движка не от id.
Freespace 2 (1999)
Как бы наследник франшизы Descent, но не совсем. Кампания и мультиплеер проходят полностью в космическом пространстве.
Прекрасный пример, как публикация исходников продляет жизнь игр на годы. К игре выпускаются всяческие наборы нового контента и апгрейды.
The Operative: No One Lives Forever (2000)
У движка LithTech история долгая, хотя он и находится в тени более известных Quake и Unreal engine. Я особенно не рылся в исходника NOLF, но я подозреваю, что там есть лишь исходники самой игры, но не графического движка. И однозначно там не будет частей, связанных с работой на PlayStation 2.
А жаль – разработка для PS2 в наши дни должна выглядеть для программистов инопланетным делом, поскольку она гораздо сильнее подходила в методу ориентации на данные, чем это делают современные API.
MechCommander 2 (2001)
Разработчик: FASA Interactive
Издатель: Microsoft
Платформа: Windows
Исторически Microsoft и открытые исходные коды вместе не уживались. Но на склоне лет ситуация начинает смягчаться. Всё-таки приятно видеть, что большие компании приходят к более открытым взглядам на вещи – все эти наработки имеют нулевую коммерческую ценность, они ценны лишь исторически.
В прошлом году даже были выпущены исходники ранних версий MS-DOS и Word, что было неслыханным делом лет 30 назад.
Doom 3 (2004)
Разработчик: id Software
Издатель: Activision
Платформа: Windows / Mac / Linux / Xbox / PS3
Code review: fabiensanglard.net
Если вы хотите изучить движки современных игр высшего класса, то Doom 3 – это один из наилучших примеров. На время выхода он был инновационным во многих областях. Метод использования моделей высокого разрешения на элементах низкого разрешения в игре сейчас является стандартом для коммерческих игр. В исходнике есть много всего интересного – одна лишь система обработки физики достойна изучения, в частности, отслеживание столкновений.
Это первая игра от id, написанная на С++. Прошлые игры из-за использования С несли в себе простоту. Doom 3 тоже довольно простой, но заметно уже изменение его вектора движения.
Также игра (печально) известна использованием трафаретных теней при расчёте освещения. Можно спорить, был это интересный эксперимент или поле для дальнейшей работы, но сегодняшние игры предпочитают использовать карты теней. Возможно, эта техника когда-нибудь ещё пригодится.
Gish (2004)
Разработчик: Cryptic Sea
Издатель: Chronic Logic / Stardock
Платформа: Windows / Linux
Gish был примечателен физикой «мягкого тела» и интересным подходом ко времени. Интересно разбираться в его исходниках и выяснять, как что работает. Никаких сторонних физических движков тут нет.
Интересно, что игра полностью написана на С – сейчас это редко встречается. Это не самый аккуратный из всех виденных мною исходников, но это хороший пример того, как игра может не превращаться в гигантскую расползшуюся базу кода с сотнями внешних зависимостей.
Canabalt (2009)
Разработчик: Adam Saltsman
Издатель: Semi-Secret / Beatshapers / Kittehface
Платформа: Flash / iOS / PSP / Android / Ouya
Не самая сложная игра, ну и что? Если вы хотите научиться делать игры, начинайте с простого – вот с этого.
Прототипирование заняло 5 дней, портирование на iOS – 10. Пример превращения простой идеи в достойное выражение. Это как бы возвращение 8-битной эпохи, когда еженедельно могли появляться новые жанры. Жаль, что с тех пор люди предпочитают клонировать идеи, а не творить самостоятельно.
Canabalt показывает, насколько вещи можно сделать просто, если захотеть.
Что я упустил
Нужно упомянуть ещё несколько игр. Они не выпускали исходников, но подверглись фанатскому обратному инжинирингу. Это, конечно, не то же самое, что настоящие исходники, но тоже может быть интересным:
Не попали в рейтинг
Сорцы следующих игр утекли в сеть нелегально, поэтому они не попали в зал славы:
Half Life 2
Falcon 4.0
ReVolt
Turrican III
Mr. Nutz: Hoppin’ Mad
Trespasser (ладно уж, вот вам обзор кода)
Как я портировал DOS игру
Введение
Хочу сразу предупредить тех, кто подумает: «Сейчас кучу игр портирую, прочитав эту статью». Как говорится, не верь всему, что пишут на Хабре. Процесс реверса — это как процесс программирования, только, на мой взгляд, муторнее и дольше.
Портирование игр требует некоторых специфических знаний. Вы должны знать ассемблер, устройство .exe файла и какой-нибудь более высокоуровневый язык программирования, например C или C++ (или другой) хотя бы на среднем уровне и немного уметь работать с IDA. Этих вопросов я касаться не буду.
Если вы всё-таки решили портировать какую-то старую игрушку, или просто получить её исходники, советую выбирать самую-самую ностальгическую и интересную.
По умолчанию, в статье под операционной системой будет пониматься Windows.
План действия
У меня был какой-то план и я его придерживался:
Открыть им экзешник
Разобрать, что делают все неизвестные функции, ну или как можно больше их
Параллельно дать всем переменным удобоваримые названия
Набраться терпения. Процесс разбора очень долгий
Создать ассемблерный файл с исходным кодом
Создать проект в %your_favorite_ide%, подключить ассемблерный файл и поправить его, чтобы он скомпилировался
Доработать ассемблерные инструкции на ваш вкус, чтобы запустить приложение, если исходники от Windows версии
Написать какой-то код на %your_favourite_language%, чтобы заменить ими ассемблерные DOS процедуры вывода на экран, прослушивания звука, инициализации окна и т.п.
В идеале все процедуры можно переписать и удалить ассемблерный файл
Наверное это самый быстрый путь, чтобы получить рабочий код, не переписывая его полностью. Пусть и на ассемблере.
Кстати, я пробовал по-другому. После разбора всех ассемблерных процедур, пытался переписать на языке высокого уровня, а также пытался написать саму игру с нуля, но быстро понял, что это займёт минимум несколько лет, которых я потратить не могу.
Также этот план подходит, чтобы получить исходники Windows приложения (начиная от версии Windows 95 и заканчивая Windows 10), хотя это может противоречить лицензии на использование этого приложения, следует иметь это в виду.
Немного подробнее по каждому пункту плана я напишу ниже.
1. Добыть дизассемблер
В наше время есть несколько мастодонтов дизассемблирования, это IDA и Ghidra и ещё какие-то. На мой взгляд, IDA пока выдаёт лучшее качество дизассемблированного кода, у неё лучше работает перевод ассемблерного кода в псевдокод, она лучше поддерживает старые библиотечные DOS функции.
Существует бесплатная версия IDA free, но она работает не совсем стабильно. У меня как-то пропала многочасовая работа по реверсу, версия free загубила свой файл проекта. Pro очень дорогая, но для разового проекта, как этот, я гонял IDA у друга, чего и вам советую.
2. Открыть экзешник
Как правило, экзешники внутри упорядочены по своей структуре, с самого начала идёт заголовок(и), затем последовательность может быть разная: код, инициализированные данные, и неинициализированные данные, таблица импорта, которой может не быть (только если вы хотите открыть программу под Windows). Нам понадобятся код и данные. Таблицу импорта придётся всю вырезать и заменить её include-ми.
Если у вас экзешник от Windows версии (например плохо работающий на современных системах экзешник от Windows 95), то вам считайте повезло. Будет сравнительно легко получить запускаемый исходный код, а что самое главное, ещё и быстро. Если от DOS с использованием 32 битного расширителя, т.е. с dos4g, dos4gw расширителями, то вам полуповезло. Если от 16 битного DOS приложения, то лучше про IDA забыть и написать всё с нуля, может быть только посмотреть как распаковываются ресурсы в коде.
Я взял экзешник от DOS игры Bedlam 2: Absolute Bedlam. Эта игра необоснованно забыта, должна была выпускаться тем же издателем, который выпустил в своё время DOOM и Duke Nukem 3D и была очень качественной. Но, в итоге, что-то пошло не так и официального релиза не было. Игра была слита недоделанной с отсутствующими уровнями, кривым балансом, некоторыми багами и отладочной информацией для левел-дизайнеров. Также, не случился релиз версии для Windows 95, который уже был годом ранее для предыдущей, первой части Bedlam.
К сожалению, не весь софт можно дизассемблировать по этическим и лицензионным причинам. Сейчас Bedlam 2 относится к так называемому ПО abandonware, забытому программному обеспечению, которое не поддерживается разработчиками, которых сейчас уже, возможно, и нет в живых. Фирма разработчик Mirage Technologies и издатель GT Interactive уже не существуют, а игру невозможно купить. Лицензия на серию Bedlam, скорее всего ещё кому-то принадлежит, я даже догадываюсь кому, но не буду их сюда призывать.
Поскольку лицензионного соглашения игры Bedlam 2 я не нашел, а на заставке игры не запрещается игру дизассемблировать, то погнали. Экзешник собран 29.10.1997г., чуть менее 25 лет назад. Тогда очень редко кто заморачивался упаковкой и обфускацией кода для защиты от дизассемблинга. Но можно проверить.
DiE показывает, что это MSDOS LE экзешник, собранный компилятором Watcom C. Никаких упаковщиков применено не было.
Собственно, мне повезло, это оказалось 32 битное приложение с применением 32 битного расширителя dos4gw. Ещё один признак, указывающий на это, это наличие dos4gw.exe в папке с игрой.
IDA должна выдать что-то типа того. Она сама определила main функцию, определила все известные ей библиотечные функции и выделила их синим цветом. Псевдокод работает (клавиша F5).
Если у вас не так и нет библиотечных функций. Вероятно стоит попробовать открыть файл с другими настройками, например как MS-DOS executable или выбрать другой тип процессора, например 486й. Бывает так, что библиотечных функций вообще нет, поскольку DOS программа пишется на основе прерываний — своих встроенных функций. Благо IDA умеет их помечать.
Если не работает псевдокод. К сожалению, он работает только в 32 битных приложениях, но есть и лайфхак. Открыть файл как 16 битное приложение MS-DOS executable, а затем в сегментах (Shift-F7) выставить сегмент кода как 32 битный. Уж не знаю какое будет качество псевдокода, не проверял. Но это работает, если приложение точно написано с 32 битным расширителем, но по каким-то причинам помечается как 16 битное.
3. Разбор функций
В списке функций, в IDA, очень много непонятных процедур SUB_xxxxxx, где xxxxxx — просто смещение. Необходимо разобраться, что они делают и дать им удобоваримые названия. В будущем это очень поможет с правкой исходников.
Советую весь разбор вести в псевдокоде. Так проще. Если из него изменять имена глобальных переменных и имена функций, то они автоматически изменятся и в ассемблерном листинге. Псевдокод хоть и удобен, но это лишь инструмент, поэтому в ассемблерный листинг придётся частенько заглядывать.
Есть несколько техник, как определить, что делает процедура (ну или функция, кому как нравится). Можно найти все строковые переменные, посмотреть, откуда они вызываются и что с ними делается. Как правило они расположены в сегменте инициализированных данных, придется их просто поискать.
Например, на рисунке выше показаны описания ошибок инициализации видео и мыши. Стоит просто лишь выделить название строковой переменной (выделено фиолетовым цветом на рисунке, IDA их автоматически создаёт) и нажать X, IDA выведет все ссылки, где используется эта строковая переменная.
Также, следующий способ узнать, что делает процедура, это найти известные вам библиотечные функции, например printf, sprintf, fopen, fread, и т.п. Посмотреть, откуда они вызываются и там уже догадаться, что выводится на экран или открывается и считывается, в зависимости от библиотечной функции.
В псевдокоде можно указать какие аргументы есть у функции, сколько их и какой их тип. Надо лишь выделить название функции и нажать Y. Формально это никак не скажется в дальнейшем на ассемблерном листинге, но поможет структурировать псевдокод.
Чтобы определить, сколько вообще у функции аргументов, на ассемблере, надо обратиться к соглашению о вызовах. Как правило, определить с каким соглашением была скомпилирована программа труда не составит, надо просто посмотреть на вызов процедур. Например, в моем случае, это очень похоже на fastcall, хотя Watcom его в то время не поддерживал. Наверняка использовалась ваткомовская директива #pragma aux для использования всех доступных регистров, в последовательности EAX, EDX, EBX, ECX, ESI, EDI, EBP. А если их не хватает, то переменные загружаются в стек справа налево. Например, вызов процедуры вывода картинки из файла numbers.bin на экран выглядит следующим образом. Точкой с запятой выделены комментарии.
Зачастую IDA неверно определяет количество аргументов. Чтобы узнать какие переменные точно можно удалить из аргументов функции, можно зайти в процедуру в ассемблерном листинге и глянуть, что расположено в начале и в конце процедуры.
В данном примере, регистры EBX, ECX, ESI, EDI, EBP никак не используются для передачи параметров в процедуру, потому как запоминаются в начале и затем восстанавливают своё состояние из стека в конце. Поэтому из аргументов их можно удалить (в псевдокоде клавиша Y, выделив функцию, и просто стереть ненужные аргументы). А EAX и EDX содержат входные параметры.
Не все функции имеют одинаковое соглашение о вызовах. Например, библиотечные функции могут иметь свои соглашения, отличные от всего пользовательского кода. Например, функция printf использует cdecl и принимает свои параметры через стек.
Иногда вам будут встречаться процедуры nullsub_X, которые состоят всего лишь из одного retn. Это следствие оптимизации программы, означающее выход из какой-либо процедуры, которая прыгнет с помощью jmp на адрес nullsub_X. Если же это будет что-то вроде call nullsub_X, то на это можно просто не обращать внимание.
Иногда IDA неверно определят границы функции из-за её оптимизации, точнее неверно определяется конец функции. Из-за этого ломается весь псевдокод, поэтому придётся вручную задавать границы в листинге. Верный признак того, что IDA неправильно определила границы функции это пустой вывод в псевдокоде. Как правило, это ничего не значит для дальнейшей сборки и на этапе первого запуска и отладки это выявляется.
У каждого реверсёра есть свои любимые техники, но зачастую просто используются и комбинируются все по ходу разбора программы.
4. Работа с переменными
Чтобы упростить работу по разбору функций, желательно переименовать все переменные в ней.
Если вы видите byte_xxxxxx, word_xxxxxx, dword_xxxxxx и т.п. — это названия переменных, в области памяти программы. Они могут как иметь инициализированное значение, так и нет. Примеры объявления некоторых переменных приведены ниже.
Естественно, всем переменным надо дать удобоваримые названия. И не просто x, y, а, например, player_pos_x, player_pos_y и т.п. Если в чём-то есть сомнение, можно к названию переменной добавлять что-то вроде _maybe. К названиям функций это тоже относится.
Как видно из листинга кода выше, не всегда понятно, массив это, или указатель, или переменная или двойной указатель. С размерностью массивов тоже не всё сперва ясно. Иногда в коде встречается что-то типа:
И тогда понятно, что dword_4E7ED6 + 2 — это начало массива на 1184 байта. Внутри clear_buffer, кстати, обычный memset(*, 0, );, это я переименовал, для понимания.
Но иногда встречается и такое:
IDA неверно определила константу 0x57800 как указатель loc_577FE + 2. К сожалению, все такие места придётся найти и поправить, иначе пересобранная программа будет крашиться в этом месте, или где-то рядом.
Как правило, довольно легко определить, константа это или переменная. Просто 2 раза кликнув на нее, можно попасть в сегмент данных (есть вероятность, что это константа, тогда такие места надо пометить до дальнейшей отладки программы) или в сегмент кода, тогда это точно константа и, выделив её, правой кнопкой мыши в ассемблерном листинге, надо это указать (или клавиша Q).
Ещё один пример в псевдокоде:
int3 здесь — точно константа, просто IDA определила константу 0x10000 как адрес прерывания int 3, которое расположено по этому адресу.
Кстати, псевдокод пестрит переменными v0, v1, v2. которых нет в ассемблерном листинге. Так IDA помечает регистры и локальные переменные. Их тоже лучше переименовывать для упрощения работы.
И да никогда не следует давать глобальным и локальным переменным одинаковые имена. Потому как с компиляцией будут проблемы.
5. Наберитесь терпения
Серьезно. Процесс разбора функций и переменных очень долгий. Возможно понадобится полгода-год, чтобы разобраться. При этом, полностью всё переименовать невозможно, но хватит и процентов семидесяти.
6. Создание ассемблерного файла
Сначала надо определиться, где будете кодить. Я выбрал Visual Studio, с поддержкой MASM ассемблера. Каких-то определенных сложностей для Linux пользователей тоже нет. Есть и MASM компиляторы для Linux, например JWasm.
Прежде чем создать ASM файл необходимо выполнить несколько шагов. Последовательность не обязательна.
IDA поддерживает синтаксис TASM и некий Generic for Intel (IDA — Options — General — Analysis). С каждым из них не всё так гладко, как хотелось бы, поэтому оставляем по умолчанию Generic for Intel.
IDA — Options — Assembler Directives. Убрать галочку ASSUME.
Развернуть все библиотечные функции. По умолчанию, IDA сворачивает их в одну строку. И если они свёрнуты, то так и попадут в ASM файл свёрнутыми. View — Unhide all.
Необходимо что-то сделать со структурами. Сложность в том, что современный MASM не поддерживает идовское объявление структур. Надо либо удалить их все из списка структур, либо использовать специальный ключ MASM компилятора «/Zm» для совместимости. Если решите оставить структуры, если, конечно, они у вас есть, нужно их развернуть, потому как они по умолчанию тоже сворачиваются в одну строку. View — Unhide all в структурах.
Убедиться, что вы не давали одинаковые имена локальным и глобальным переменным. К сожалению, какого-то совета, как это сделать нет. При компиляции вылезет.
Убедиться, что нет какого-то специфичного не MASM синтаксиса, который можно поправить прямо в IDA. Например, у меня было такое в секции данных:
MASM не поддерживает такую запись unicode текста, поэтому по каждой такой записи правой кнопкой мыши — Undefine.
Ещё одна проблема создания ассемблерного листинга — это данные, принятые за код. И код за данные. У меня была такая памятная строка в секции кода, выглядящая изначально как набор непонятных инструкций. Если есть сомнения, нужно смотреть дамп памяти, там есть представление в ASCII символах.
Кажется, всё. Можно генерировать ASM файл. Делается это довольно просто, File — Produce File — Create ASM file. Может получиться что-то типа filename.exe.asm. Тогда удалите из названия .exe. Сохраните также куда-нибудь копию сгенерированного файла и не трогайте её в дальнейшем, хоть в гит отдельным бранчем, потом пригодится для вспоминания, что было изначально в каком-либо месте.
7. Создание проекта и компиляция файла
Теперь необходимо поправить все ошибки в файле и просто его скомпилировать. Ничего страшного, если исходный файл от DOS программы, запускать мы его не планируем, пока что. Если файл от Windows, тогда можно будет и запустить.
В VS нужно включить MASM в зависимостях сборки (Правой кнопкой по проекту — зависимости сборки). См. рисунок.
После этого всё должно компилироваться. Или по крайне мере пытаться компилироваться. После 100 ошибок MASM компилятор выдаст, что на этом его полномочия всё и прекратит работу.
Первое, что надо сделать, это поправить весь синтаксис, не характерный для MASM. Ассемблерный файл должен напоминать следующую структуру
При этом надо удалить все объявления секций, которые у нас были созданы идой. В MASM секции объявляются директивами .CODE, .DATA и .DATA?. При этом, последовательность секций значения не имеет. Т.е. вместо
Также нужно удалить строчки public start и end start, которые означали начало и конец программы. Вместо этого в MASM используется метки start:, end start. Их ставить не обязательно, если вы не будете запускать программу. Точнее, эти метки start указывают точку входа в программе.
Теперь быстренько пробежимся над ошибками, которые выдает компилятор. Их все придётся устранить одну за другой.
instruction prefix not allowed
invalid combination with segment alignment : %num_bytes%
undefined symbol : loc_xxxxxx
Очень много неопределенных меток loc_xxxxxx и def_xxxxxx, которые MASM не может найти, хотя они присутствуют в файле. Причина, почему такое происходит, это локальные метки для MASM и область их поиска ограничена процедурой proc, end proc. Такие ошибки чаще всего возникают, если:
IDA неверно определила границы процедуры. Решение: просто передвинуть конец процедуры end proc на верное окончание процедуры. Начало процедуры лучше не двигать, если вы 100% не уверены в правильности вашего решения.
Компилятор соптимизировал часть дизассемблированной программы и процедуры вызывают одни и те же участки кода в других процедурах. Как правило, оптимизации подвергается окончание процедур с вереницей одинаковых pop-ов. Есть два решения этой проблемы. Либо сделать метку глобальной, просто добавив второе двоеточие, вроде loc_xxxxxx. либо раскопировать весь оптимизированный код по местам, где он должен стоять. Быстрее и безошибочнее на этом этапе, мне кажется, метод с двумя двоеточиями.
Компилятор дизассемблируемой программы поместил метки оператора switch (характерно для программ на языках C, C++) за пределами процедуры. Если вы видите очень много меток def_xxxxxx или loc_xxxxxx, а в комментария от IDA написано что-то вроде ;jumptable XXXXXX case X, то это тот случай. Как правило, метки располагаются рядом с процедурой целым списком, вроде:
Нужно переделать так, чтобы эти метки располагались в процедуре:
Опять же константы, принятые за метки, про которые я ранее писал. Например, метка loc_186A0, в данном случае, — это константа 100000. На константу намекает круглое значение и то, что по адресу 0x186A0 находится какой-то участок кода, а не дата.
На метку же намекают все типы прыжков. Что-то вроде jmp loc_16860 всегда будет меткой.
undefined symbol : %segment_name%
undefined symbol : %some_name%
missing operator in expression
Нажимая на ошибку, попадаем на строчку
Нужно стереть все large. MASM их не понимает. Смысл остаётся тот же.
use of register assumed to ERROR
Нажимая на ошибку, попадаем в место вроде:
Регистр GS никак не определён или компилятор думает об этом, поэтому надо незадолго до процедуры, а лучше где-нибудь вначале сегмента с кодом написать:
initializer magnitude too large for specified size
Кажется это MAX_DOUBLE, использующееся, для вспомогательной функции форматирования (s)printf. Измените на:
На этом всё. Ошибки закончились, файл компилируется. Линкер ругается на отсутствие функции main, потому как не может найти точку входа в программу, мы ведь не добавляли start:, end start метки.
8. Разные доработки в asm (компиляция Windows программы)
Расскажу вкратце на примере одной функции. Для остальных всё будет аналогично. Например, где-то в коде вызывается библиотечная функция создания окна.
Нужно найти процедуру proc CreateWindowExA, которая перенаправляет на библиотечное импортированное название функции, и удалить её. Нужно также удалить все оффсеты и переменные, содержащие CreateWindowExA в сегменте данных. Нужно удалить любые похожие имена вроде __imp_CreateWindowExA. Нужно вообще всё удалить, с именем CreateWindowExA, чтобы кроме вызова функции больше ничего не было.
Нужно скачать и установить masm32 SDK. В папке masm32\include и masm32\lib содержится всё, что нам нужно. Нужно подключить user32.lib и user32.inc в проект и указать пути до библиотек и инклюдов в свойствах проекта. Это делается следующим образом.
В самом начале ассемблерного исходника, перед секцией кода, дописать:
Если всё сделано правильно, то линкер не будет ругаться на отсутствие функции. Всё скомпилируется, если вы, конечно же, повторите шаги для всех импортируемых функций.
Кастомный инклюд сделать очень просто. Все имена функций из exports.txt надо переписать в виде, как показано ниже. Это и будет наш инклюд.
Более подробно, можно посмотреть как реализованы вызовы библиотечных функций в репозитории первой части игры Bedlam, Windows версии.
9. Замена DOS процедур на аналоги
Самое время написать что-нибудь на C++. Необходимо заменить все библиотечные DOS функции и прерывания на кросс-платформенные аналоги. С библиотечными функциями понятно, что это такое, этот термин не менялся с того времени. Прерывания же представляют собой встроенные DOS функции, которые выполняются, прерывая собой пользовательскую программу.
Существует великое множество прерываний. Например, считывание/запись файла, выделение памяти, вывод на экран, вывод звука и т.п.
Вызываются прерывания довольно просто. Ассемблерный пример:
Прерывания могут принимать параметры из регистров, например тип функции прерывания, указатель на буфер, размер и т.п.
Замене подвергнутся процедуры вывода графики на экран, вывода звука, инициализация мыши и клавиатуры, прерывания по таймеру. Их придётся реализовать высокоуровневом языке, например C++. Хотя подойдет любой язык, способный вызывать функции из сторонних библиотечных файлов. Структура программы будет следующая: необходимая инициализация на C++, затем запуск main функции из ассемблерного файла, который будет дергать нужные функции ввода-вывода, написанные на С++.
Здесь спецификатор __cdecl опущен, поскольку это платформозависимая запись, а в настройках проекта все функции, по умолчанию компилируются как __cdecl. Литера «C» указывает компилятору использовать имена функций в C стиле.
Для вызова C++ функции из ассемблерного листинга необходимо указать компилятору, что имя функции в C-стиле, указать соглашение о вызове. Соглашение платформозависимо, написано в windows стиле. Например, есть некая функция сложения:
В заголовочном export_to_asm.h:
Вызвать функцию на ассемблере можно так (характерно лишь для соглашения __cdecl и 32 битного кода):
Какого-то универсального совета, как переписать вывод графики, звука, опрос мыши, клавиатуры, реализовать таймеры и т.п. не существует.
С графикой в DOS всё довольно сложно. Дело в том, что ранний и поздний DOS существенно отличаются в плане вывода графики. Необходимо будет найти что-то типа указателя на экранный буфер, который линкуется в память видеокарты. Кстати, таких буферов может быть несколько. Пока один наполняется, второй отображается на экране. Также буферы могут быть разбиты на страницы, размерностью кратной степени двойки байт.
В игре Bedlam 2, используется экранный буфер, размерностью 640*480 байт, которое равно разрешению экрана. Каждый байт этого буфера — это пиксель с индексом цвета 256-цветовой палитры. Палитра представляет собой отдельный массив байтовых элементов Red, Green, Blue. Каждое число в палитре поделено на 4, как я понял, для создания градиентных переходов, использующих простой сдвиг палитры. Сами палитры хранятся в файлах с расширением .pal.
Во время игры используется другой буфер, больше экранного, на который рисуются все спрайты, затем этот буфер копируется с обрезкой в экранный буфер. Это сделано для того, чтобы можно было корректно рисовать спрайты на границах экрана.
Мышь и клава — это кажется самое простое, что пришлось переписать. В процессе разбора asm-исходников были найдены переменные: координаты мыши, координаты левого/правого клика, состояние кнопок мыши, а также массив bool-ов, содержащий нажатия кнопок клавиатуры, где индекс массива равен ordinary keyboard scancode (на вики нет описания, если вкратце, одна кнопка представляет один определенный байт — ordinary scancode). Потребовалось просто записывать в эти переменные свои значения каждый цикл игры.
Другие интересности
Исходный код игры писался во времена, когда 486й процессор считался очень хорошим. Разработчики боролись за каждый такт. Соптимизированно всё и вся. Из-за быстродействия, использовался язык C, хотя C++ в то время уже было лет 10-15.
В коде игры не использовалось ни одного float и double значений, либо иного значения с плавающей запятой.
Там, где умножение необходимо, используются степени двойки и сдвиги. Там, где этого недостаточно, умножение соптимизированно в адскую последовательность сдвигов и сложений.
Взятие синуса — довольно затратная процедура в то время. Поэтому синус берётся из таблицы Брадиса файла sintable.bin по индексу-значению.
Для повышении точности этого хака, иногда входные параметры сдвигаются на 8 бит влево. А выходной — вправо.
Кстати, по поводу сдвигов. Используются везде. Координаты объекта, например игрока, сдвинутые на 8 бит вправо будут давать координаты плитки тайла, на котором находится игрок.
Номер, извлечённый из missionX.tot буфера по координатам плитки тайла, будет равняться номеру картинки тайла по координатам x, y из графического файла-пака missionX.bin.
Некоторые рандомные значения тоже заранее вычисляются и заносятся в массив. Например, шум картинки юнита, когда он получает повреждения.
Управление памятью — отдельная история. Нелегко сделать игру, когда у тебя ограничение в восемь мегабайт на графику и логику и два с половиной на звук, который можно отключить, чтобы сэкономить.
Кстати, фабрики созданы и для врагов, снарядов, взрывов, обломков, останков, бонусов, возможно чего-то ещё. Максимальное количество объектов в каждом случае константное.
Всё игровое поле, т.е. поверхность и постройки на нём — набор ромбовидных тайлов. Как я писал, номера картинок тайлов уровня, проще говоря карта, находится в буфере missionX.tot. Хотите добавить огня на карту, просто запишите номер картинки огня в этот буфер. Конечно же, это происходит в оперативной памяти. Хотите разрушить постройку, занесите картинку разрушенной постройки туда же. Игра так и работает.
Послесловие
Видео портированной второй части, переработанной мной под различные разрешения. Конкретно, в этом случае, 1080p.
Git Bedlam 1 (довольно мало ей занимался, поэтому просто сырые компилирующиеся сорцы). Можно посмотреть, как сделан импорт библиотечных функций.
Git Bedlam Tools (инструмент для просмотра BIN файлов, прослушивания RAW, MRW файлов игры).
Ссылка на реверс тут (файлы IDA).
К сожалению, эти игры сейчас официально не купить вообще нигде, поэтому ссылки для ознакомления с Windows версиями вы найдёте здесь: 2 часть (для запуска понадобится пакет «Microsoft Visual C++ 2015 Redistributable x86»).
К сожалению, рабочей Linux версий нет. Вторую часть игры я собрал под Linux, но дальше главного меню игра крашится. У меня нет инфраструктуры, чтобы отладить приложение, наверняка там ничего сложного нет. Возможно, есть какие-то проблемы с путями файлов или графикой.
PS. Хочу поменять стек на C++. Джун нужен кому? Просьба в личку.