Найти в Дзене
Marksics

Как я менял местами персонажей Ultimate Mortal Kombat 3

Для многих, включая меня, Ultimate Mortal Kombat 3 была одной из самых любимых игр детства. Давно хотелось разобраться, как она устроена, но всё никак не доходили руки. Теперь настал подходящий момент. Я решил начать с чего-то простого и наглядного, а именно поменять местами портрет и самого персонажа. В этой статье подробно рассмотрим процесс замены бойца и его изображения на экране выбора, на примере изменения Noob Saibot на Human Smoke. Этот выбор не случаен — портрет Noob Saibot самый простой, использует минимум цветов и тайлов. Далее шаг за шагом опишу, какие действия выполнял, чтобы успешно заменить персонажей. Для работы я буду использовать Американский ром Ultimate Mortal Kombat 3 (USA) На момент подготовки статьи в DataCrystal имелась лишь одна подкатегория — RAM Map, поэтому другие разделы, особенно Tutorial, я не использовал. Для начала решил изучить уже известные адреса в игре. Зашел на датакристал, обнаружил, что у данной игры была только одна подкатегория RAM map. В ней н
Оглавление

Введение

Для многих, включая меня, Ultimate Mortal Kombat 3 была одной из самых любимых игр детства. Давно хотелось разобраться, как она устроена, но всё никак не доходили руки. Теперь настал подходящий момент. Я решил начать с чего-то простого и наглядного, а именно поменять местами портрет и самого персонажа.

В этой статье подробно рассмотрим процесс замены бойца и его изображения на экране выбора, на примере изменения Noob Saibot на Human Smoke. Этот выбор не случаен — портрет Noob Saibot самый простой, использует минимум цветов и тайлов.

Далее шаг за шагом опишу, какие действия выполнял, чтобы успешно заменить персонажей.

Для работы я буду использовать Американский ром Ultimate Mortal Kombat 3 (USA)

Изменение последовательности персонажей

На момент подготовки статьи в DataCrystal имелась лишь одна подкатегория — RAM Map, поэтому другие разделы, особенно Tutorial, я не использовал.

Для начала решил изучить уже известные адреса в игре. Зашел на датакристал, обнаружил, что у данной игры была только одна подкатегория RAM map. В ней нашёл интересное значение по адресу 126, где хранится информация о текущем выбранном персонаже:

P1 Character:

0x00 = Kano

0x01 = Sonya

0x02 = Jax

0x03 = Nightwolf

0x04 = Unmasked Sub-Zero

0x05 = Stryker

0x06 = Sindel

0x07 = Sektor

0x08 = Cyrax

0x09 = Kung Lao

0x0a = Kabal

0x0c = Shang Tsung

0x0d = Liu Kang

0x0e = Smoke

0x0f = Kitana

0x10 = Jade

0x11 = Mileena

0x12 = Scorpion

0x13 = Reptile

0x14 = Ermac

0x15 = Classic Sub-Zero

0x16 = Human Smoke

0x17 = Noob Saibot

0x18 = Rain

Сначала я решил попытаться испытать удачу и найти эту последовательность напрямую. Для этого я взял изображение с выбором персонажей и составил последовательность, предполагая, что она будет идти слева направо сверху вниз.

Экран выбора персонажей
Экран выбора персонажей

Я взял значение первых 3-х персонажей(18 13 05) и попытался их найти. Ожидаемо, безуспешно. Тогда решил проверить вариант с 16-битными значениями, предположив, что каждому бойцу выделено по два байта. Поиск по последовательности 00 18 00 13 00 05 дал результат — единственное совпадение по адресу DFC8.

Найденная последовательность персонажей в notepad++
Найденная последовательность персонажей в notepad++

Посмотрел дальше, и вся остальная последовательность совпадает. Для теста попробовал поменять Rain на Shao Khan (0x1B), и оказалось, что где-то еще лежат дефолтные значения для первого и второго персонажей, так как чтобы изменения вступили в силу, нужно сначала переключиться на другого персонажа, а потом снова перейти на предыдущего.

Результат замены
Результат замены

Чтобы не тратить время на поиск нужных значений, учитывая, что в роме их 2234 и 920 соответственно, я решил взять какой-нибудь хак, в котором эти значения изменены. Затем я сравнил изменённый файл с оригинальным с помощью WinMerge и нашёл нужные места. Для этого я выбрал Ultimate Mortal Kombat 3 - Arcade Hack v0.6 by Nemesis_c

Экран выбора персонажей в Ultimate Mortal Kombat 3 - Arcade Hack v0.6 by Nemesis_c
Экран выбора персонажей в Ultimate Mortal Kombat 3 - Arcade Hack v0.6 by Nemesis_c

Изучив отличия, я обнаружил, что изменения касаются адресов D43E и D446. Если хотите изменить начальных персонажей, нужно редактировать эти значения.

Поиск и анализ данных портретов

В отличие от замены списка бойцов, замена портрета — более сложная задача, так как нужно знать, как строится графика на экране выбора персонажа. Так как раньше я не изучал Ultimate Mortal Kombat 3, решил собрать любую доступную информацию — игра популярная, значит, кто-то уже наверняка пытался исследовать её устройство.

Для начала открыл ROM в BizHawk, чтобы определить, как именно отображаются портреты: это спрайты, фон или, возможно, window(так обычно в играх не делают, но мало ли). Анализ показал, что экран выбора делится на два слоя — A и B, так обычно делают, чтобы можно было использовать больше цветов.

VDP viewer
VDP viewer

Таким образом, портреты оказались частью фона, и следующим шагом было выяснить, хранятся ли они отдельно или объединены в одно изображение.

Попытка найти сырые данные тайлсета ничего не дала — неудивительно, ведь объём игры всего 4 МБ, и все персонажи не могли бы поместиться без компрессии.

Тогда я обратился к форуму Emu-Land, так как там часто встречаются редкие технические сведения, которые нельзя найти на других ресурсах. Наткнулся на такую тему Mortal Kombat 3 Ultimate Hack. В ней обсуждалось что можно изменить в игре. Быстро пошарив тему наткнулся на такой скриншот, иллюстрирующий внутреннюю структуру графики.

Структура графики
Структура графики

Данное место в роме находится по адресу 3ee3ba. Это оказался скрин компании Willem, который показывается в начале игры. Я попробовал занулить пару значений тайловой карты, чтобы убедиться, что это точно скрин Willem, хорошо, что для его появления не нужно долго ждать и можно всё быстро проверить. После зануления и запуска игры, на экране Willems я получил ошибку.

Так удалось понять принцип организации данных:

  1. TileSet - графика
  2. Refs - ссылки на TileSet и TileMap, а также их размеры
  3. TileMap - Тайловая карта

Следовательно, требовалось найти нужный Refs для экрана выбора бойцов. В процессе я заметил, что между Refs и TileMap присутствует большой блок нулей. Мне так до сих пор не удалось понять, зачем он нужен, ведь без него можно было бы сэкономить память, но это можно использовать при анализе.

Далее я предположил, что, может, не все данные сжаты: возможно, палитры лежат в открытом виде. Из просмотрщика слоёв стало видно, что используются третья и четвёртая палитры — третья для слоя B, четвёртая для A.

Палитры на экране выбора персонажей
Палитры на экране выбора персонажей

Я обнаружил их по адресам 5c40e и aa392. Значит, палитры не зашифрованы, что упрощает задачу. Тогда я решил искать все участки, ссылающиеся на палитру слоя B, но это ничего не дало.

Я начал искать Refs, которые находятся рядом с палитрами. Это решение основывалось на предположении, что всё должно быть расположено в непосредственной близости друг от друга.

Начал с 5c40e. Алгоритм поиска был такой: сначала переходил по адресу 5c40e, а потом искал последовательность из 16 нулей(00 00 00 00 00 00 00 00 00 00 00 00 00 00 00) и смотрел, чтобы было похоже, как на скрине со структурой. Однако нужных совпадений не было. Тогда решил поискать рядом с другой палитрой(aa392). Это сразу же дало результат по адресу aa732, где значение 00 0a a3 b4 оказалось ссылкой на TileSet. Я перешел к нему и попробовал изменить байт по адресу aa3c1 на 00, так как до этого я заметил, что все TileSet и TileMap начинаются с 00 01 или 00 02, что, по всей видимости, указывает на заголовок, изменять который нельзя — иначе игра выдает ошибку.

Вот что в итоге получилось:

Сломаный портрет Рейна
Сломаный портрет Рейна

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

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

Из предыдущего раздела стало ясно, что для замены существующих портретов местами нужно всего лишь поменять их ссылки и, при необходимости, размер.

Вот например я поменял местами портрет Джакса на портрет Рейна:

Джакс вместо Рейна
Джакс вместо Рейна

Замена на свой портрет

Чтобы продемонстрировать процесс на практике, я выбрал конкретный пример — замену портрета Noob Saibot на Human Smoke.

Этот выбор не случаен — портрет Noob Saibot самый простой, использует минимум цветов и тайлов.

Для такой замены нам понадобится либо программа, способная выполнять сжатие и распаковку графики, либо самому узнать и реализовать данные алгоритмы.

После изнурительных поисков мне всё-таки удалось найти подходящий инструмент на сайте Chief-Net.ru — утилиту Ultimate MK3 Codec.

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

Так как замена касалась именно портрета, я решил использовать только слой B — на нём все тайлы портрета уникальны. Это ограничило количество доступных цветов, поэтому пришлось адаптировать свой портрет под палитру слоя B, сократив число оттенков до 13.

Затем я применил собственный конвертер, который принимает изображение и палитру и формирует два бинарных файла — TileSet и TileMap в формате Sega. После этого данные нужно было зашифровать и вставить в ROM. Однако с первой попытки у меня ничего не вышло, получившийся блок занимал больше места, чем оригинал. Оставалось два пути: вставить данные в конец РОМа, но тогда это увеличит его размер, либо найти место в роме, которое не используется(если оно есть) и вставить туда.

К счастью, такое место действительно существовало: благодаря тому же форуму удалось узнать, что по адресу 3EE47A хранятся неиспользуемые портреты из оригинального Mortal Kombat 3, включая даже Shiva. До сих пор неизвестно, почему разработчики их оставили, но нам это на руку.

После конвертации и вставки данных изображение появилось, но с искажениями:

Сломаный портрет Нуба
Сломаный портрет Нуба

Я начал разбираться в ситуации и выяснил, что расшифрованные данные карты занимают в два раза больше места, чем у меня.

Декомпреснутая карта портрета Нуба
Декомпреснутая карта портрета Нуба

Я думал, что программа некорректно дешифрует тайловые карты, и решил провести проверку. Дешифровал карту, изменил значение первого элемента в TileMap на +1, зашифровал и загрузил в ром. В результате первый тайл на слое А изменился.

Измененый первый тайл в портрете Нуба
Измененый первый тайл в портрете Нуба

Позже выяснилось, что менять карту вовсе не обязательно, ведь на слое B все тайлы уникальны. Нужно было лишь найти в TileSet область, относящуюся к слою A, обнулить её и вставить свои тайлы в позицию слоя B. Эти блоки располагались в середине и конце набора, поэтому вставку пришлось выполнить дважды.

После нескольких попыток всё заработало:

  • Первая не удалась из-за ошибок в конвертере при работе с палитрой.
Первая попытка
Первая попытка
  • Вторая дала частичный результат.
Вторая попытка
Вторая попытка
  • Третья выявила проблему — два чёрных цвета в палитре воспринимались как один, из-за чего смещались индексы.
Третья попытка
Третья попытка

Исправив это, с четвёртой попытки удалось добиться идеального результата, и портрет теперь корректно отображается на экране выбора.

Четвертая попытка
Четвертая попытка

Также всё корректно отображается на экране VS, и в самой игре всё прекрасно работает.

Работа в игре
Работа в игре

Заключение

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

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

Полная версия статьи находиться здесь