Вечная жизнь в играх своими руками

Хак на диске


Модификация игр в памяти— мощная штука, но все-таки не свободная от ограничений. Программы, защищенные различными протекторами (типа star-force) активно сопротивляются снятию дампа, а под linux нормальных дамперов вообще нет! В этих (и многих других) случаях приходится прибегать к альтернативному методу — правке файлов состояния игры ("сейвов").

Тактическая стратегия выглядит как обычно: сохраняется в saved_1, перемещаемся без потери здоровья (патронов) и сохраняется в saved_2, затем стреляем один раз (тратим несколько процентов здоровья) и сохраняемся в saved_3. Сравниваем полученные файлы нашей утилитой fck.exe и смотрим различия. Выбрав наиболее вероятных "кандидатов", правим их в hex-редакторе, загружаем исправленный файл в игру и, если патроны/здоровье не изменились, правим следующий байт и т. д.

Вернемся к DOOM'у. Подготавливаем три сейва (doomsav0.dsg, doomsav1.dsg и doomsav2.dsg) и сравниваем их друг с другом. Опс! Все они имеют разный размер: 2440, 2586 и 2650 байт. Плохо дело! Судя по всему, сэйв имеет сложную структуру и простое побайтовое сравнение скорее всего ничего не даст, поскольку ячейки, отвечающие за хранение патронов (здоровье) окажутся расположенными по различными смещениям.

Расшифровка структуры сэйв файлов — сложное, но очень увлекательное дело, "заразившее" множество светлых умов. Основным оружием становится интуиция и нечеткий "скользящий" поиск — мы ищем совпадающие (или просто похожие) фрагменты и корректируем смещения с привязкой к ним.

В частности, doomsavX.dsg имеет следующую структуру: сначала идет заголовок, содержащий имя сэйва, версию игры и прочую лабуду.

0000000000:  32 00 F4 77 00 00 00 00 ¦ 00 00 00 00 00 00 00 00  2 Їw

0000000010:  2B 7E 9C F7 00 00 00 00 ¦ 76 65 72 73 69 6F 6E 20  +~Ьў    version

0000000020:  31 34 32 00 00 00 00 00 ¦ 61 66 33 32 00 52 37 59  142     af32 R7Y

0000000030:  65 73 00 B3 19 31 00 8F ¦ 29 32 30 00 A8 43 4F 66  es ¦v1 П)20 иCOf


0000000040:  66 00 BE 9F 4E 6F 00 6E ¦ 77 59 65 73 00 C8 37 4E  f -ЯNo nwYes L7N

Листинг 7 заголовок сэйва

За заголовком расположен какое-то бессистемный блок, всегда начинающийся со смещения 100h:



0000000100:  31 34 35 37 36 33 32 38 ¦ 0D 00 C8 00 02 00 64 00  14576328d L O d

0000000110:  00 00 90 01 28 00 90 01 ¦ 02 00 28 00 00 00 2C 01    РO( РOO (   ,O

0000000120:  64 00 00 00 07 00 00 00 ¦ 03 00 00 00 00 DC 05 01  d   •   ¦    -¦O

0000000130:  00 00 00 04 03 01 00 4C ¦ 00 20 00 00 00 00 00 00     ¦¦O L

Листинг 8 "бессистемный" блок

К бессистемному блоку примыкает характерная структура с косыми потоками рожиц. Ее смещение и размер различны и варьируются в очень широких пределах. Мы еще не знаем, за что она отвечает, но что бы здесь ни располагалось, вести сравнение нужно не с начала файла, а с начала этой самой структуры:

0000000140:  FF 00 00 01 01 01 01 00 ¦ 01 01 01 08 00 01 1C 01     OOOO OOO• OLO

0000000150:  0A 00 01 01 01 0B 00 01 ¦ 01 01 0C 00 01 01 01 10  0 OOO> OOO+ OOO>

0000000160:  00 01 01 01 11 00 01 01 ¦ 01 12 00 01 01 01 13 00   OOO< OOO¦ OOO!

0000000170:  01 01 01 14 00 01 01 01 ¦ 15 00 01 4C 01 16 00 01  OOO¶ OOO§ OLO¦ O

Листинг 9 косые потоки рожиц — что они значат?

А как определить это самое начало? Очень просто — точно так, как астрономы определяют переменные и вспыхивающие звезды! Звездное небо не остается постоянным и некоторые звезды изменяют свой блеск (иногда — значительно). Но как найти их среди тысяч других? Очень просто — проецируем один звездный снимок на стену, затем удаляем его из проектора и вставляем другой, снятый чуть позже, добавясь, чтобы звезды располагались на тех же самых местах, а теперь быстро-быстро меняем снимки один за другим. Переменные звезды начинают характерно мерцать!

Используем эту технику для поиска отличающихся байт! Нам потребуется только наш верный хвост и FAR. Подгоняем курсор к doomsav0.dsg и давим <F3> (view), а затем <F4> (hex-mode).


Нажимаем <+> для перехода к следующему файлу (doomsav1.dsg), а затем <-> для возвращения к предыдущему. Повторяем эму операцию несколько раз, убеждаясь, что косые потоки рожиц смещаются на значительные расстояния, поскольку начинаются с разных смещений. Нажимая <Alt-8> (goto) изменяем стартовое смещение файла doomsav1.dsg так, чтобы рожицы перестали прыгать. Словно мы крутим "синхронизацию" на осциллографе или накладываем отпечатки пальцев друг на друга, добиваясь наибольшего совпадения. В моем случае, разница в базовом смещении "рожиц" составила 7 байт. То есть, чтобы они засинхронизовались один файл необходимо просматривать начиная со смещения F0h, другой — F7h. ОК! Рожицы совпадают и никаких различий между ними не обнаруживается! За что же они отвечают?



Рисунок 13 синхронизация "рожиц" в FAR'е



Рисунок 14 быстро переключаясь между файлами по <+>/<-> ищем изменившиеся ячейки

Возвращаемся в игру и, не сходя со своего места, убиваем одного монстра. Сохраняемся. Ага! Различий по прежнему нет. Значит, "рожицы" отвечают не за трупы. Обращаем внимание, что по мере прохождения игры рожиц становится все больше и больше. Так, может быть, это и есть картографирование? Проверяем свою гипотезу и действительно — стоит нам войти в новый сектор, как сразу же добавляется новая порция рожиц. Интересно: хранят ли они только карту или еще и состояния дверей? Это легко выяснить экспериментально!

За рожицами начинается соврем другая структура данных, в которой на первый взгляд нет никакой закономерности и которая чудовищно изменяется между двумя соседними сохранениями. Логично предположить, что здесь сосредоточена святая святых — описание объектов игрового мира. Но как во всем этом разобраться?

0000000740:  00 50 05 00 00 30 0E 40 ¦ FF FF BF 01 00 00 00 00   P¦  0d@  ¬O

0000000750:  03 26 00 00 68 2E F1 00 ¦ 00 00 08 00 00 00 08 00  ¦&  h.ё   •   •



0000000760:  09 00 00 00 30 07 00 00 ¦ 30 0E 40 FF FF BF 38 03  0   0•  0d@  ¬8¦

0000000770:  04 00 00 00 01 00 00 00 ¦ 00 03 20 00 00 34 2F F1  ¦   O    ¦   4/ё

0000000780:  00 00 00 08 00 00 00 08 ¦ 00 0A 00 00 00 E0 06 00     •   • 0   р¦

0000000790:  00 50 0D 40 FF FF BF 01 ¦ 00 00 00 00 03 24 04 00   Pd@  ¬O    ¦$¦

00000007A0:  00 30 F1 00 00 00 70 00 ¦ 00 00 70 00 0B 00 00 00   0ё   p   p >

Листинг 10 фрагмент структуры, описывающей состояние игрового мира

При внимательном осмотре дампа мы обнаружим, что константа FFFFh встречается намного чаще, чем остальные. Это ключ к понимаю структуры файла, но… где тот замок, куда его вставить? Смотрим: константы расположены на различном расстоянии друг от друга, значит мы имеем дело со структурой переменного размера или со списком, завершаемым "термирующим" символом FFFFh. Если эта структура, то где-то должен храниться ее размер, выражаемый в байтах, словах или двойных словах. Как найти его в дампе?

Возьмем две ближайших константы, расположенные по смещению 748h и 76Bh. Как нетрудно подсчитать, их разделяет 23h байта. Следовательно, размер структуры не может выражаться ни словами, не двойными словами (23h не кратно двум), а только байтам. Ищем число 23h в окрестности наших констант. Его нет! Поэтому, можно предположить, что FFFFh используется в качестве тремирующего символа, то есть служит знаком конца списка. Остается только написать программу, отображающую содержимое списков в удобночитемом виде — тогда искать различия будет намного проще. Однако, это довольно сложная задача, решение которой требует уймы времени и терпения. Зато потом мы сможем "убивать" любых монстров или добавлять новых, подкладывать аптечки и другие артефакты, словом, творить чудеса, но это будет потом.

Сейчас же мы ограничимся тем, что пополним запас патронов, здоровья и брони, а так же дадим герою все оружие из которого мыщъх предпочитает совсем не BFG, а обыкновенный дробовик, причем одностволку!



Вместо сравнения сэйвов, мы используем альтернативный подход, называемый "прямым константным поиском". Допустим, у нас сложилась следующая ситуация: здоровье — 68%, броня — 95%, пистолетных патронов — 73 (200 max), патронов для дробовика — 24 (50 max). Переводим 68 и 95 в шестнадцатеричную систему исчисления, получаем 44h и 5Fh соответственно. Отгружаем игру в doomsav.dsg и загружаем этот файл в hiew. Давим <F7> (search) и ищем 44h где оно есть (внимание! при поиске чисел > 255 необходимо помнить, что младший байт располагается по меньшему адресу и потому ищется в обратном порядке, то есть для поиска 1234h в hiew'е необходимо ввести 34 12).

Ячейка с искомым значением тут же обнаруживается по смещению B4h. Может это и не здоровье, но… рядом с ней лежит 5Fh, а это, как мы помним, наша броня:

000000B0:  00 00 00 00-44 00 5F 00-01 00 01 0A-00 00 00 00      D _ O O0

000000C0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00

000000D0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00

000000E0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00

000000F0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 30                 0

Листинг 11 фрагмент сэйв-файла, с ячейками хранящими здоровье (подчеркнуто) и броню (выделено полужирным)

Исправляем оба числа на FFFFh и загружаем исправленный сэйв в игру. Держите мой хвост! У нас получилось!!!!



Рисунок 15 c такой броней и здоровьем никакие враги не страшны!

Теперь разберемся с оружием и патронами. Добавить патроны — несложно, но прежде необходимо заполучить оружие. Обычно оно хранится в игре в переменных-флагов: одно значение соответствует состоянию "оружие есть", другое — "оружия нет". Флаги бывают битовыми, байтовыми, словными и двухсловными. Битовый флаг хранит все оружие в одной ячейке. Например, 01h (0000000001b) означает пистолет, 02h (0000000010b) – дробовик, а 03h (0000000011b) — пистолет с винчестером.


Ладно, завязываем с теорией и переходим к активным действиям.



Рисунок 16 бензопила — не самое мощное оружие, но чуть позже с ее помощью мы завладеем BFG

Возвращаемся в DOOM. На самый первый уровень. Выходим на балкон и видим пилу. В смысле бензопилу. Сохраняемся, затем не совершая никаких движений сохраняемся еще. Берем пилу и сохраняемся в третий раз (см. рис. 16). Мы получаем следующие файлы: doomsav0.dsg, doomsav1.dsg и doomsav2.dsg. Натравливаем на них fck.exe и видим результат:

raw offset    doomsav1.dsg  doomsav2.dsg

00000008h:    CEh    35h

00000009h:    DDh    CFh

0000000Ah:    1Ch    22h

0000000Ch:    E8h    15h

0000000Dh:    2Ch    B1h

0000000Eh:    D9h    98h

00000014h:    51h    54h

000000BAh:    01h    07h

00000121h:    0Bh    44h

00000123h:    01h    02h

00000127h:    00h    47h

00000128h:    00h    FDh

00000129h:    01h    00h

0000012Bh:    00h    20h

0000012Ch:    00h    01h

00000135h:    00h    47h

00000136h:    00h    FDh

00000137h:    01h    00h

00000139h:    00h    20h

0000013Ah:    00h    01h

0000013Dh:    03h    83h

0000028Ah:    24h    26h

000002A9h:    00h    08h

000002ABh:    01h    00h

Листинг 12 до и после взятия бензопилы ("подозреваемая" ячейка выделена полужирным)

Да тут до фига изменений! Даже не сообразишь с чего начинать… Будем рассуждать логически. Если оружие храниться с байтовом флаге, то он скачкообразно поменяет свое значение с FALSE (ноль) на TRUE (обычно 1 или FFh) или наоборот. Если это битовый флаг, то после захвата бензопилы, нужная нам переменная будет отличаться всего одним битом.

Начнем с ячейки 12Ch – она поменяла свое значение с 00h на 01h. Допустим, этот флаг хранит бензопилу. Тогда логично предположить, что остальное оружие хранится где-то рядом (хотя это бывает и не всегда), однако, соседние с не ячейки меняют свое значение в сумбурно-хаотичном порядке и на флаги не тянут.

А вот ячейка 13Dh очень даже похожа! Смотрите — сначала в ней было 03h (00000011b), а после взятия бензопилы — 83h (10000011b).


Скорее всего, первый справа бит хранит оружие типа "кастет", второй — пистолет, а последний — пилу. Чтобы проверить наше предположение необходимо добыть еще одно оружие, например, дробовик и посмотреть.

raw offset    doomsav1.dsg  doomsav2.dsg

00000000h:    32h    33h

000000A8h:    05h    06h

000000BAh:    01h    02h

0000010Ch:    00h    10h

00000129h:    0Bh    13h

00000145h:    83h    87h

00000642h:    00h    01h

0000064Ch:    00h    34h

Листинг 13 до и после взятия дробовика ("подозреваемая" ячейка выделена полужирным)

Вот черт! Ячейка по смещению 13Dh ушла в туман, но зато ячейка по смещению 145h изменилась с 83h (10000011b) на 87h (10000111b), то есть после взятия дробовика добавился третий бит. Это явно она! Именно здесь храниться все оружие, которое добыл игрок. Вот только ее смещение непостоянно и в разных файлах оно сильно неодинаковое… но мы-то знаем, что в doomsav1.dsg/doomsav2.dsg оно гарантировано равно 145h! Загружаем doomsav2.dsg в hiew и меняем 87h на FFh (все оружие).

Открываем исправленный сэйв и смотрим что из оружие у нас есть. У нас есть все! Вот только патронов не хватает, а что толку от оружия, если оно не стреляет? Приходится хачить!

Как мы помним, у нас есть 73/200 пистолетных патрона и 24/50 патронов для дробовика. Переводим эти числа в hex-форму — 49h/C8h, 18h/32h и смотрим где они в дампе:

00000100:  31 34 35 37-36 33 32 38-49 00 C8 00-18 00 32 00  14576328; E

> 2

00000110:  00 00 2C 01-00 00 32 00-00 00 00 00-00 00 00 00    ,O  2

00000120:  05 00 00 00-02 00 00 00-00 13 00 01-00 00 00 00  ¦   O    ! O

00000130:  00 01 00 00-00 20 00 00-00 00 00 00-00 00 00 01   O             O

Листинг 14 ячейки, ответственные за хранение пистолетных патронов (текущее/максимальное количество) и зарядов для дробовика

Ага! Лежат рядышком! А следом за ними идет 00h 00h 2Ch 01h. Ничего не напоминает? С учетом обратного порядка байт 2Ch 01h равно 12Ch или 300 в десятичном исчислении — максимально возможное количество зарядов плазмогана/BFG на данном уровне.


А два нуля — это количество самих зарядов. Тогда последовательности 00h 00h 32h 00h ничего не остается как отвечать за число ракет (00 ракет в наличии, 50 — максимум). Меняем все эти байты на FFh и получаем максимальный боезапас:

00000100:  31 34 35 37-36 33 32 38-FF FF FF FF-FF FF FF FF  14576328yyyyyyyy

00000110:  FF FF FF FF-FF FF FF FF-00 00 00 00-00 00 00 00  yyyyyyyy

00000120:  05 00 00 00-02 00 00 00-00 13 00 01-00 00 00 00  ¦   O    ! O

00000130:  00 01 00 00-00 20 00 00-00 00 00 00-00 00 00 01   O             O

Листинг 15 фрагмент исправленного сэйва, дающий все оружие с кучей патронов

Загружаем исправленный сэйв в DOOM, нажимаем <7> для выбора BFG и встаем на тропу войны.



Рисунок 17 мыщъх на тропе войны

Остальные игрушки ломаются схожим образом. Вот, например, UFO, которое кладет все сейвы в каталоги GAME_X, причем кладет их очень много! Почти полсотни файлов! К счастью, утилита fc.exe из штатной поставки Windows, поддерживает джокеры и позволяет сравнивать множество файлов сразу: "fc.exe /b GAME_X\* GAME_Y\* > log", избавляя нас от необходимости делать это вручную. Утилита fck.exe джокеров не поддерживает (кто возьмется ее доработать?), но можно поступить проще, воспользовавшись простым пакетным файлов следующего содержания:

for %%A IN (*) DO fck.exe game_1\%%A game_2\%%A game_3\%%A >> log

Листинг 16 командный файл, позволяющей утилите fck обрабатывать множество файлов

Сравнение сэйвов показывает, что при добавлении новых исследований в проект, один или несколько байтов в research.dat устанавливаются в единицу. Наблюдая за изменениями легко установить, что файл имеет блочную структуру и размер блока равен 16h байтам, а флаг, "разрешающий" исследовательский проект находится по смещению 08h от начала блока.

Приведенная ниже программа открывает все исследования, в том числе и Cydonia/L'Tech без которого игра не может закончится, зачастую становясь непроходимой.



#define MAX_BUF_SIZE (10*1024)

main(int x, char **argv)

{

       int a,t,c, x=0; FILE *fsave, *fback; unsigned char gamebuf[MAX_BUF_SIZE];

       unsigned char fn_save[MAX_BUF_SIZE]; unsigned char fn_back[MAX_BUF_SIZE];

      

       #define BLOCK_SIZE                0x16

       #define off_ENABLE_RESEARCH       8

      

       #define FN_SAVE "\\research.dat"

       define FN_BACK "\\research.da_"

      

       if (x<2)return printf("USAGE: ufo1-2AlienContainmentBugFix.exe path_to_save\n"\

                     "example: ufo1-2AlienContainmentBugFix.exe GAME_1\n");

      

       printf("* * * ENABLING Cydonia or L' Tech City in RESEARCH LIST * * *\n");

      

       strcpy(fn_save, argv[1]); strcat(fn_save, FN_SAVE);

       strcpy(fn_back, argv[1]); strcat(fn_back, FN_BACK);

      

       // делаем резервную копию

       fsave = fopen(fn_save,"rb"); c = fread(gamebuf, 1, MAX_BUF_SIZE, fsave);

       fback = fopen(fn_back, "wb");fwrite(gamebuf,1,c, fback);

      

       // добавляем Cydonia/L'Tech

в исследовательский проект

       for (a = 0; a < c; a += BLOCK_SIZE) gamebuf[a + off_ENABLE_RESEARCH] = 1;

      

       // сохраняем изменения в файле

       fclose(fsave); fsave = fopen(fn_save,"wb"); fwrite(gamebuf, 1, c, fsave);

      

       printf("============================================================\n"\

              "now see BASES > RESEARCH > Add New Project > Cydonia/L'Tech\n");

}

Листинг 17 программа, дающая Cydonia/L'Tech в UFO-2


Содержание раздела