Пойнтеры. Часть вторая.
Mefistotel   
24.01.2013 .

 

ПОИНТЕРЫ. ЧАСТЬ ВТОРАЯ. (автор: Mefistotel, помощь и корректировка: Djinn, оформление: Guyver)

Введение

Идея написания этого документа пришла спонтанно. Изучив всю имеющуюся в рунете информацию по пойнтерам, я пришёл к выводу, что её недостаточно. В основном рассмотрены простейшие примеры на NES. Лишь в двух документах говорится о пойнтерах на SNES. Но методика, которая там предлагается, является скорее исключением из правил. И я бы не советовал вам так искать пойнтеры. :) Именно поэтому я решил написать документ, который бы отражал основные аспекты поиска пойнтеров на различных игровых платформах. Данная информация будет полезна ромхакерам, выбравшим новую для себя платформу. Для понимания сути прочитанного надо хотя бы знать, что такое хексредактор и как им пользоваться, инженерный режим калькулятора Windows и прочие мелочи, которые должен знать каждый человек, называющий себя ромхакером. Ну и конечно, вы должны прочитать информацию по пойнтерам на сайтах Шедевр и Magic Team, чтобы не возникало вопросов в духе "а что делать с найденными пойнтерами". Итак, начнём.

NES (Nintendo Entertainment System)

Методы поиска пойнтеров на эту платформу описаны больше всего. И не мудрено, практически все начинали именно с неё. В этом разделе я расскажу вам о простой формуле. Можете почитать любопытную доку про указатели на сайте Griever-a для лучшего восприятия нижеизложенного. Стоит кратко напомнить, что указатели на эту платформу 2-х байтные и могут быть расположены выше или ниже текста, или находиться в программном коде и быть разбросанными по рому.

В первой части этого документа Шеф рассказывал о загадочной системе SetOff X000 и немного упоминал о пойнтерах в игре Adventure in Magic Kingdom, где младший и старший байт пойнтера были записаны в разных местах (двух массивах). Рассмотрим пример пойнтеров, у которых в одном массиве сначала идут младшие части, а затем старшие. Для изучения возьмём ром Megaman IV (U) (PRG0) [!]. Думаю, писать о том, как искать текст при помощи хексредактора не стоит. Вы и сами должны это знать. А если вы этого не знаете, то попрошу покинуть помещение. Скажу лишь, что сначала пойнтеры в этой игре я нашёл только при помощи дебага, хотя, при небольшом усердии можно было обойтись и без него, так как указатели были совсем рядом с текстом.
 
mm4

Текст начальной заставки (HOUSEHOLD ROBOTS ROCK AND ROLL WERE CREATED BY MASTER ROBOT DESIGNER, DR.LIGHT, AND WERE ENJOYING THEIR PEACEFUL DAYS и пр.) начинается с адреса 0x377D6. Стопбайтом является байт FF, байтом переноса строки - FD. Напоминаю, что текст внутри промежутка (начало предложения; FF) можно менять как угодно, переставляя лишь байт переноса строки FD. Это применимо для этой игры и для множества других, где используется так называемый SEQUENTIAL TEXT. Пойнтер на NES рассчитывается по формуле:

((Offset – 0x10)) and 0x1FFF) + 0x8000

  • AND - кнопочка на калькуляторе Windows справа под C
  • 0x1FFF или 0x3FFF. Это значение зависит от размера банка в маппере. Если размер 0x2000, то 0x2000 - 1 = 0x1FFF, если 0x4000, то 0x4000 - 1 = 0x3FFF
  • 0х8000, 0xA000, 0хС000 или 0хE000. Всё зависит от маппера, и от того в какую область памяти загружается банк рома c текстом. Чтобы точно узнать все значения, можно или методом подбора найти их, или посмотреть документацию маппера, который используется в разбираемой игре. И не надо изучать термины Standart Header или SetOff X000. Всё просто и понятно.
Вычисляем пойнтер в соответствии с формулой на первую букву в слове HOUSEHOLD:

((0x377D6 - 0x10)) and 0x1FFF) + 0x8000 = 0x97C6

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

Вычисляем пойнтер в соответствии с формулой на первую букву в слове DR.LIGHT:

((0x3781С - 0x10)) and 0x1FFF) + 0x8000 = 0x980С

Вычисляем пойнтер в соответствии с формулой на первую букву в слове THEN:

((0x3784C - 0x10)) and 0x1FFF) + 0x8000 = 0x983C

Пробуем найти указатели, но снова не выходит. Тогда вспоминаем, что старшие и младшие части указателей на NES могут храниться нестандартно. И для поиска вводим только младшие части этих трех указателей, то есть С6 0С 3С. Вбиваем эту последовательность в поиск и ищем в роме. Наш поиск выдает её по адресу 0x377BC. И это значит, что мы нашли место, где хранятся младшие части наших пойнтеров. Теперь нам осталось найти только старшие части. Если внимательно посмотреть, то вы сразу их заметите. Они расположены сразу же за младшими частями и начинаются с адреса 0x377C9 – 97 98 98...
 
dd3
 
Для закрепления материала рассмотрим пример стандартных пойнтеров, которые хранятся в одной табличке. Я заметил, что в играх от Technos очень часто используется именно такие указатели. Поэтому используем ром Double Dragon III - The Sacred Stones (U) [!]. По адресу 0x1906E начинается текст, появляющийся после первого боя (brett: listen! i tried to stop them but they were too powerful for me. they`re great fighter и пр.). Система здесь такая, сначала идёт байт, обозначающий количество символов в строке, затем сама строка. То есть указатель будет ссылаться именно на этот байт количества символов в строке, а не на первую букву в ней. Найдём указатель:
 
((0x1906E - 0x10)) and 0x1FFF) + 0x8000 = 0x905E

Воспользуемся поиском и сразу же находим нужную нам последовательность 5E 90 по адресу 0x18FA6. Внимательно посмотрите на байты справа: 7790 8F90 0000 A490… Это и есть наша табличка пойнтеров. Указатели идут по три подряд, затем следует интервал в два байта 00 00. Если хотите, то можете проверить, что указатель на вторую строку будет равен 0x9077, на третью 0x908F и так далее.

Зачастую младшие и старшие части указателей идут отдельно в коде, то есть через интервал друг от друга. Рассмотрим такое случай на примере адски сложной игры Ghosts 'N Goblins (U) [!]. В случае гибели героя появляется следующий экран:
 
gg
 
Если вы хотите перевести GAME OVER как «ИГРА ОКОНЧЕНА» в своём переводе, то без поиска указателей вам никак не обойтись. Строка с данным текстом (PLAYER 1 GAME OVER) начинается с адреса 0x1CBD0. Стопбайт, разделяющий строки отсутствует, как и байт переноса строки. Вместо этого используется отдельный байт, отвечающий за длину строчек. Для начала рассчитаем указатель по формуле:
 
(0x1CBD0 - 0x10)) and 0x1FFF) + 0xC000 = 0xCBC0
 
0xC000 – текст загружаются в это адресное пространство, чтобы это определить необходимо посмотреть код загрузки строки с помощью дебаггера. Также возможно просто перебрать комбинации 0х8000, 0xA000, 0хС000, 0хE000.
 
gg
 
Если попробовать найти последовательность C0 CB в роме, то поиск не принесёт результата. Как известно, младшая часть указателей на NES равна последнему байту адреса строки за вычетом хедера. Совпадающий байт С0 находится немного выше текста по адресу 0x1CBB3.
 
gg
 
Чтобы убедиться в том, что это искомое значение, возможно заменить его на байт С1 и посмотреть результат в игре:
 
gg
 
Нужная строка изменилась – произошёл сдвиг на один байт вперёд. Значит, младшая часть указателя найдена верно. При небольшом знание ассемблера станет понятно, что перед байтом C0 идёт команда его загрузки в аккумулятор, а после него команды загрузки этого значения из аккумулятора в оперативную память:
 
gg
 
A9 - опкод команды LDA (загрузка младшей части указателя в аккумулятор),
C0 – младшая часть нашего указателя,
85 - опкод команды STA (загрузка младшей части указателя из аккумулятора в память),
26 - адрес в нулевой странице памяти.
После этой комбинации идёт последовательность:
 
gg
 
A9 - опкод команды LDA (загрузка младшей части указателя в аккумулятор),
CB – старшая часть нашего указателя,
85 - опкод команды
STA (загрузка младшей части указателя из аккумулятора в память),
27 - адрес в нулевой странице памяти.
 
Из примера понятно, что части указателя идут в коде через интервал. Теперь вернёмся к вопросу о длине строки. Оригинальная надписьGAME OVER равны 0x9 символам, а русский вариант второй строчки ИГРА ОКОНЧЕНА равен 0x0D (13) символам. Найденный указатель поможет нам переместить строку в свободное место рома, к примеру, на адрес 0x1C8B1 (поменять местами с надписью 2 PLAYER), но без изменений длины строки конечная задача всё равно не будет выполнена. Поищем байт длины строки 09 и найдём его по адресу 0x1CA13. Необходимо сменить его на 0x0D, чтобы выводилось по 13 букв в каждой строчке.

Перенесём строку на новый адрес 0x1C8B1, поменяем указатель на 0xC8A1 и изменим байт длины на 0x0D.
 
gg
 
В результате получим:
 
gg
 
Можно ещё найти координату строк по Х, чтобы их оцентровать, но это уже к пойнтерам не имеет отношения. Схожая система используется и в игре Hunt for Red October, The (U) (REV0) [!].
 

SEGA MEGA DRIVE/GENESIS
На Сеге пойнтеры искать проще простого. Чаще всего они абсолютные 4-х байтные, но реже бывают и относительные 4-х и 2-х байтные. Относительные пойнтеры также могут принимать отрицательные значения. Абсолютные пойнтеры могут указывать в любое место рома. Пойнтеры могут находиться как выше текста, так и ниже, быть в таблице или разбросаны по рому.
Заголовок у ромов Sega Genesis нам вообще не нужен и переворачивать найденные значения указателей не нужно, так как на приставке применяется порядок записи байтов от старшего к младшему (Big-Endian). Рассмотрим на примере пойнтеры из игры Scooby Doo Mystery (JUE).
В этой игре присутствуют как относительные, так и абсолютные 4-х байтные пойнтеры. Пойнтеры могут повторяться по несколько раз по разным адресам, потому что в разных игровых местах текст может повторяться и следовательно может применяться несколько пойнтеров на него. Сначала найдём ОТНОСИТЕЛЬНЫЕ 4-Х БАЙТНЫЕ ПОЙНТЕРЫ. Они могут располагаться относительно чего-либо: начала рома (00000000), адреса начала текста в блоке, адреса начала таблички пойнтеров или каких-либо данных перед таблицей пойнтеров. Если таблица пойнтеров перед текстом, то можно просто самому рассчитать смещение.
Текст первого сценария (Black`s Hotel) начинается с адреса 0x14199C, конец этого блока с текстом - 0x145EC6. Стопбайтом является байт 00. Смещением, относительно которого располагаются все строки, является как раз адрес начала текста, то есть 0x14199C. Для поиска пойнтеров вам может пригодиться хексредактор Winhex с его поиском значений во всём файле. Найдём пойнтер на второе предложение в этом блоке (I made you a sandwich, but you were gone, so I ate it.). Берём адрес первой буквы во втором предложении и отнимаем от него адрес начала блока с текстом:

0x1419B2 – 0x14199C = 0x16

Не забываем, что пойнтеры состоят из 4 байтов, то есть значение будет не 0x16, а 0x00000016. Теперь осталось только найти этот пойнтер в роме. Используйте любой хексредактор с поиском Hex-значений. Но следует сказать, что Translhextion16c не может адекватно находить значения, начинающиеся с 00. Я использую Winhex. Переходим по адресу 0x14199С, нажимаем: Поиск -> Поиск Hex значений. Ставим галочку "Сохранить позиции" и указываем место поиска "Вниз".
Поиск выдаёт нам целых 16 адресов. Ничего страшного. Записываем адрес первого в списке (0x146A78) и ищем следующий пойнтер.

0x1419E9 – 0x14199C = 0x4D

Наш единственный пойнтер 00 00 00 4D находится по адресу 0x146AE6. Он недалеко расположен от первого указателя, и можно предположить, что мы верно нашли значение. Если есть сомнения, просто меняем значение пойнтера и проверяем в игре. Найдём ещё один указатель:
0x1419FE – 0x14199C = 0x62

Снова поиск выдаёт нам несколько адресов, берём первый - 0x146B90. Опять же, он недалеко расположен от предыдущего.
Таким образом, мы можем найти все пойнтеры на этот блок. Подведём итог. Поинтеры предметов и диалогов идут после текста. У пойнтеров предметов есть закономерность появления - через четыре байта, у пойнтеров текста - через 14 байт. Они хранятся в подпрограммах вывода сообщений и читаются программой вместе со всеми параметрами отображаемого текста, например, цветом. Но пойнтеры текста идут не по порядку, как предложения в роме, а вперемешку. Пойнтеры названий комнат хранятся отдельно и находятся ВЫШЕ блока с текстом. Идут строго через 16 байт.

В этой игре, как я уже говорил, присутствуют и АБСОЛЮТНЫЕ 4-Х БАЙТНЫЕ ПОЙНТЕРЫ. С ними всё намного проще. Текст игровых команд (Shaggy, Scooby, Talk, Push, Give и пр.) начинается с адреса 0x2FE50 и кончается 0x2FE9F. Стопбайтом является значение0x0000. Если поиск указателей относительно чего-либо ничего не дал, тогда пробуем искать абсолютные пойнтеры. Если пойнтер абсолютный, это значит, что его значение равно адресу начала текста, на который мы и ищем указатель, то есть берём последовательность00 02 FE 50 (адрес первой буквы в слове Shaggy) и ищем его в роме. Не забываем, что мы ищем 4-х байтный указатель, поэтому и нули слева. Поиск показывает, что данная последовательность встречается один раз и находится выше текста, по адресу 0x5B74. Через 8 байт по адресу 0x5B80 идёт следующий пойнтер 00 02 FE 57 на слово Scooby. Адрес первой буквы в слове Pull - 0x2FE5E, соответствующий пойнтер 00 02 FE 5E расположен по адресу 0xA902. Если попробовать найти указатели на Take, Open и пр. ничего не выйдет, потому что указатель лишь есть на первую букву первого слова в этом, своего рода блоке, то есть на Pull. Внутри этого блока вы можете, как угодно менять длины слов, перемещая лишь байт 0x00 вправо или влево.

Чуть ниже, начиная с адреса 0x2FECC, идёт текст, связанный с командами (No way am I gonna pull! и пр.). Указатели на него также абсолютные и находятся сразу же над текстом, с адреса 0x2FEA0. Идут в табличке без интервала. Посмотрите сами, и я уверен, вы их найдёте.

В дополнение к этому материалу посмотрим текст звукового теста, начинающийся с адреса 0x1F7082. Стопбайт - 00. Сразу же попробуем найти абсолютный пойнтер на первую букву первого звука Scooby Slurp. То есть ищем 00 1F 70 82, и находим это значение выше текста по адресу 0x9696. Это единственный указатель на блок с текстом звукового теста. Внутри этого блока можно менять длину слов, переставляя лишь байт 0x00. А если вам мало места под русский перевод, то можно перенести в свободное место весь блок с текстом, подправив указатель конечно. 

Теперь рассмотрим пример ОТНОСИТЕЛЬНЫХ 2-Х БАЙТНЫХ ПОЙНТЕРОВ.
 
fl
 
Возьмём игру Fatal Labyrinth (JU) [!]. Адрес начала текстового блока с монологами в деревеньке (dragonia,the legendary castle,has risen once more и пр.) - 0x2C94. Если не нажимать старт во время заставки, то попадешь в неё. Стопбайтом является сочетание FF00, байтом переноса строки - FF. Если введём в поиске 2С 94, то сразу найдём эти байты выше текста, по адресу 0x2C68. Но это не абсолютный пойнтер, как можно сразу подумать. Указатель находится относительно начала файла и не может указывать на все адресное пространство, потому как 2-х байтный. Это первое значение в таблице. Интервал между пойнтерами в ней составляет два байта. То есть следующий указатель 2C CA на текст "what shall we do? a ghoul stolethe holy goblet for the dragon!" находится через два байта, по адресу 0x2C6E и так далее.
В завершении темы указателей на эту платформу необходимо добавить про ОТНОСИТЕЛЬНЫЕ 2-Х БАЙТНЫЕ ПОЙНТЕРЫ, которые относительны своего адреса. То есть значение указателя равно разнице значений адреса начала строки и адреса указателя на эту строку. Такие указатели могут принимать и отрицательные значения. Ниже приведены формулы для расчёта таких указателей:

Ptr = (Offset text - Offset pointer)Offset text = (Ptr + Offset pointer)
Ptr
 – собственно пойнтер;
Offset text – адрес строки, на которую ищем указатель (0x);
Offset pointer – адрес указателя (0x)
.

У многих появятся резонные вопросов, а как вычислить значение указателя, если ты не знаешь его адреса? Или наоборот, как вычислить адрес указателя, не зная его значения? Ответ будет дан ниже, но для его понимания необходимы начальные навыки владения ассемблером под процессор М68000. Чтобы было понятнее рассмотрим пример таких указателей, встреченных в игре Langrisser II (J) (REV02) [T+Eng_M.I.J.E.T.]. После титульного экрана появляется экран ввода имени персонажа. Искать будем указатель на строку Enter Hero`s Name, расположенную по адресу 0xA7471. Стопбайтом является сочетание FF.
 

p1

Найти простым поиском указатель на эту и другие строки в данном блоке текста довольно трудно. Попробуйте, если не верите. :) Если брать примитивный метод, то придётся искать комбинации байтов, похожих на указатели, выше или ниже текста. Далее смотреть влияют ли они на текст или нет. После этого найденное сочетание прибавлять к адресу и смотреть, на что, собственно говоря, укажет полученное значение. Чтобы найти такие совпадения может помочь любая программа-корруптер (Пакостник, Visual Poganka и др.). Всё это долго и муторно, поэтому мы пойдём по научному пути.

Воспользуемся эмулятором c хорошим отладчиком Gens v2.11 + Advanced Tracer Mod v1.181 full (http://consolgames.ru/download.php?view.60), модифицированный нашим соотечественником Виктором Яковлевым. Чтобы найти указатель, нам необходимо до появления нужной строки поставить условный брейкпойнт на состояние регистра, через который будет проходить текст. На титульном экране перед нажатием кнопки Start запускаем M68K Debugger, так как показано на картинке.

p2

Специфика ромхакинга под процессор Моторола 68000 такова, что все адреса текста, графики, тайловых карт и пр. проходят через адресные регистры A0-A6. Регистр А7 занят под стек и не используется для вывода данных. Так как мы не знаем заранее, через какой регистр выводится наш текст, то расставим адрес нашей строки 0xA7471 во все регистры, кроме А7 и ставим галочки как показано на рисунке.

p3

После этого смело нажимаем ОК и запускаем игру, нажав на Start. На экране тут же появляется окно дебаггера. Это говорит о том, что какой-то из установленных брейкпойнтов сработал. Посмотрим внимательно на значения в регистрах и увидим, что в регистре А0 находится адрес нашей строки Enter Hero`s Name.

p4

Таким образом, наша строка выводится через регистр А0 и можно предположить, что и всё строки в блоке с текстом (0xA7416 – 0xA74B9) выводятся через этот регистр. Если посмотреть в левое окно, то можно увидеть, что адрес в регистр записывается командой, расположенной по адресу 0xA71DA:

p5

Комбинация 02 95 и будет указателем на нашу строку. И задаётся этот указатель в коде команды. Так как у нас данные хранятся в Big Endian и нет необходимости их свопить, ищем выше/ниже текста комбинацию 02 95 или, что надёжнее, 41 FA 02 95. Несложно догадаться, что указатель 0x0295 расположен по адресу 0xA71DC (адрес команды загрузки указателя 0xA71DA плюс 2 байта код команды LEA). Проведём проверку найденного значения:

Offset text = (Ptr + Offset pointer)Offset text = (0x0295 + 0xA71DC) = 0xA7471

Что и требовалось доказать. Указатель на предыдущую строку Sell Items (0xA7462) расположен по адресу 0xA7894 и равен 0xFBCEОн является отрицательным, так как адрес строки меньше адреса указателя.Напоследок вкратце расскажу о простой команде LEA $0295 (PC), A0.PC – это программный счетчик, указывающий на адрес следующей выполняемой инструкции. В данном случае берётся абсолютное значение 295, прибавляется к адресу следующей инструкции и получается адрес строки. То есть в роме сразу же за блоком кода расположен блок строк и 295 - это смещение нужной строки относительно текущей исполняемой инструкции в блоке кода. Разумеется, когда писался код, человек не мог предугадать через сколько байт будет эта строка. 295 вычислил компилятор и вставил в код - такой способ экономит место и позволяет адресовать быстрее, чем любой другой

Больше информации вы сможете узнать из интернета, например, здесь: http://en.wikipedia.org/wiki/Addressing_mode#PC-relative_2


SNES (Super Nintendo Entertainment System)
На данной консоли присутствуют абсолютные пойнтеры - 3 байта и относительные - 2 или 3 байта. Если игра переведена с японского фанатами, то там часто можно встретить абсолютные. Желающих при поиске указателей отнимать заголовок в 200 байтов и менять их местами попрошу покурить в коридоре. Хедер в ромах SNES вообще не нужен. Если он есть, то необходимо удалить его утилитой snestool. Этой же утилитой спокойно можно добавить его обратно. АБСОЛЮТНЫЕ ПОЙНТЕРЫ на этой приставке вычисляются по спец. формулам. Для их расчета сначала нужно узнать при помощи эмулятора тип адресации: HiRom или LoRom. Например, в Snes9X эта информация появляется при запуске игры.
Формула для получения значения пойнтера из адреса в файле (для адресации LoRom):

Ptr = (Offset and 0x7FFF) + ((Offset shr 0x0F) shl 0x10) + 0x008000 (может быть 0x808000)
  • 0x808000, 0x008000 – при поиске указателей стоит проверять оба этих значения.

Обратная формула для получения адреса в файле из значения пойнтера:

Offset = (Ptr and 0x7FFF) + (Ptr and 0x7F0000) shr 1

  • shr и shl - операторы сдвига битов, 0x0F0x10 - это количество бит, на сколько нужно изменить исходное значение.
  • AND - кнопочка на калькуляторе Windows справа под C.

В инженерном режиме калькулятора Windows операция shl выполняется кнопкой lsh, а операция shr установкой галочки на inv и кнопкой lsh, то при расчете скобки (Offset shr 0x0F) считаем так: offset Inv+lsh 0x0F, а при расчете shl 0x10: lsh 0x10

Формула для получения значения пойнтера из адреса в файле (для адресации HiRom):

Ptr = Offset + 0xC00000
 
lod
 
Рассмотрим пример АБСОЛЮТНЫХ ПОЙНТЕРОВ, встреченных в игре Liberty or Death (U) [!]. Если не нажимать старт после загрузки игры, то появится кораблик и текст (In 1585, the first colonists set sail from Great Britain и пр.) В роме он начинается с адреса0x8BB2A. В этой игре адресация HiRom, и чтобы вычислить указатель, нужно всего лишь прибавить к адресу 0xC00000:

Ptr = 0x8BB2A + 0xC00000 = 0xC8BB2A

Попробуем найти в роме последовательность 2A BB C8. Не вышло. Тогда берём два байта указателя 2A BB и ищем их. Поиск указывает на адрес 0x89F30. Проверка показывает, это то, что нужно. Ровно через три байта в программном коде идёт последний байт указателя, то есть С8. На следующие строки указатели отсутствуют, то есть это один указатель на весь текстовый блок. И если изменить его значение, то тогда сдвинутся все строки, а не одна. Если внимательно посмотреть, то поймёте, что строки здесь фиксированной длины, в 21 символ. И байт 00 отделяет одну от другой. Эти нули нельзя убирать со своих мест, иначе длина строк будет нарушена, и возникнут проблемы с выводом текста. Изменить это возможно, только переписав игровой код.
 
arcana
 
Рассмотрим пример АБСОЛЮТНЫХ ПОЙНТЕРОВ в игре Arcana (U) [!]. В этой игре адресация LoRom. Напомню, что заголовок был убран утилитой snestool (Delete Header). Текст (This is an historic land. A land where, from time immemorial, ancient legends have been passed down from generation и пр.), появляющийся сразу после загрузки игры, начинается по адресу 0xB812B. Скорее это даже скрипт, в начале содержащий управляющие байты, а потом сам текст. Стопбайтом выступает комбинация байтов 1B 01 FF 00, байтом переноса строки - 0D. Вы скажете, а как я узнал, почему адрес начала текста именно 0xB812B, а не 0xB8136 (первая буква в словеThis)? Для точного нахождения пойнтеров и проверки значений указателей для этой платформы мне пригодился эмулятор Snes9x с отладочными средствами - Geiger’s Snes9x Debugger Mark 9 и немного времени. В этой ревизии эмулятора есть функция Show Hex. Она поможет проверять нам значения найденных пойнтеров. Посчитаем пойнтер по формуле для адреса 0xB8136:

Ptr = ((Offset and 0x7FFF) + (Offset shr 0x0F)) shl 0x10) + 0x008000

Ptr = (( 0xB8136 and 0x7FFF) + (0xB8136 shr 0x0F)) shl 0x10) + 0x008000

Ptr = (0x136 + 0x170000 + 0x008000 = 0x178136

Пойнтер, как обычно, хранится в роме в обратном порядке, то есть не 17 81 36, а 36 81 17. Поиск этой последовательности ничего не даст. Можно просто рассчитать указатели на адреса раньше, а можно просто вбить в поиск два старших байта пойнтера, то есть 81 17 и пробить поиском. И чудо, такую комбинацию мы сразу же найдём немного выше, по адресу 0xB80DC. Байт (17) левее и будет верным. Проведем проверку последовательности 2B 81 17 (0x17812B):
 
Offset = (Ptr and 0x7FFF) + (Ptr and 0x7F0000) shr 1

Offset = (0x17812B and 0x7FFF) + (0x17812B and 0x7F0000) shr 1

Offset = 0x12B + 0xB8000 = 0xB812B

Вот так я и вышел на точный адрес начала текста.
Рассчитаем пойнтер для второго блока текста (ambitions. . . most were ruined и пр.):

Ptr = (( 0xB822B and 0x7FFF) + (0xB822B shr 0x0F)) shl 0x10) + 0x008000

Ptr = (0x22B + 0x170000 + 0x008000 = 0x17822B

Он находится немного выше, по адресу 0xB822B.

Мы можем проверить правильность нашего пойнтера по-другому. Запускаем ром, указанным выше эмулятором и попадаем в окно дебагера. Нажимаем кнопку Show Hex и попадаем в окно Hex Editor. В режиме просмотра (Viewing) должно стоять ROM. В левую и правую ячейки, где стоят нули, ставим значение нашего пойнтера, то есть 0x17812B, нажимаем Set Range. Сразу после этого в правом окне редактора должен появиться наш текст, на который и указывает пойнтер, а это значит, что его значение рассчитано верно.

Закрепим материл. По адресу 0x40FBD расположены надписи (Nothing here! **** The Treasure Box was empty.) Мы уже знаем, что 7F00 является стопбайтом. Чтобы перевести «Nothing here!», как "Ничего интересного", нам нужно сдвинуть вторую строку. Она начинается с адреса 0x40FD2. Вид строк: служебные байты -> текст. Рассчитаем по формуле указатель:

0x40FD2 and 7FFF + 0x80000 = 0x88FD2

Поиск последовательности D2 8F 08 ничего не даст, но мы сможем найти два младших байта указателя байта, то есть D2 8F по адресу 0x3A8B0, затем через три байта идёт старший байт указателя08 (0x3A8B5). Ещё через 6 байтов (0x3A8BC) идёт и младшие байты указателя на первую строку"Nothing here!" - BD 8F. И старший байт 08 находится также через три байта от них. Из этого примера должно быть понятно, что младшие и старшие части указателя могут быть удалены друг от друга (по аналогии с игрой Megaman на NES), поэтому нужно тщательнее пользоваться поиском и проверкой найденных данных.


GBA (Game Boy Advance)

На этой платформе существуют два вида пойнтеров: 4-х байтные АБСОЛЮТНЫЕ И ОТНОСИТЕЛЬНЫЕ 2-х и 4-х байтные. Найти так же легко, как и на Sega. Абсолютные указатели встречаются здесь гораздо чаще относительных.

Чтобы вычислить значение абсолютного указателя, нужно всего лишь к адресу в роме прибавить значение 0х08000000. И искать последовательность байт, как обычно, в обратном порядке.
D&D
 
Для примера АБСОЛЮТНЫХ указателей возьмём игру Dungeons & Dragons - Eye of the Beholder (U). Текст заставки (Greetings, Warriors. My father, Piergeiron the Paladinson и пр.) начинается с адреса 0x33A54. Стопбайтом и одновременно байтом переноса строки выступает сочетание 00 00 00 00 или 00 00. Рассчитаем указатель на этот адрес:
Ptr = 0x33A54 + 0x8000000 = 0x08033A54

Ищем последовательность 54 3A 03 08 в роме. Она находится по адресу 0x282F4. Это первый указатель в большой таблице. Пойнтеры идут без интервала, например, следующая последовательность 70 3A 03 08 является указателем 0x08033A70 на адрес 0x33A70 (My father...) и так далее.
 
Tactics Ogre

Рассмотрим ОТНОСИТЕЛЬНЫЕ указатели в игре Tactics Ogre - The Knight of Lodis (U). В этой игре сначала идёт табличка, затем сразу блок с текстом. Если возникнут затруднения, то для нахождения таблички просто можете взять два блока текста и посмотреть между ними. И сразу найдёте таблицу указателей. Теперь собственно об их расчёте. Указатели на текст относительны адресу начала их таблицы, то есть адрес таблицы плюс значение пойнтера равно адрес строки, или берём адрес первой строки и отнимаем адрес начала таблицы указателей, полученное значение меняем местами и ищем. Текст первых игровых диалогов, который, кстати, оптимизирован MTE начинается с адреса0x7843F8 (Well Dressed Young Man "Well then, did your mother make a fuss over you? и пр.). C адреса 0x7843B0 начинается табличка указателей на этот текст. Расчет значение первого указателя:
Ptr = 0x7843F8 - 0x7843B0 = 0x0048

Два байта 48 00 как раз и находятся по адресу 0x7843B0. Следующий пойнтер 0x009A стоит следом за 0x0048 (0x7843B2). Проверим это значение:

Offset = 0x7843B0 + 0x009A = 0x78444A

С этого адреса начинается вторая строка (DarkHaired Knight I dont have time for that и пр.). В этой игре, как и во многих других, все указатели рассчитываются подобным образом.

Pc Engine (Turbografx-16)

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

На этой платформе мною обнаружен один тип пойнтеров. Это двухбайтные пойнтеры. Они не сильно отличаются от пойнтеров на NES, но размер заголовка составляет 512 байт (0x200).

Механизм страничной адресации PCE делит адресное пространство процессора на 8 частей по 8 кб (т.н. 'окна') и доступ к нему происходит при помощи MMU (Memory Managment Unit). Номера страниц указываются в восьмибитных регистрах MPR0MPR7 (в MPR0 номер страницы для первых 8 КБ адресного пространства процессора, в MPR1 - для следующих и т.п.). Напомню, что 8 кб – это 0x2000 байт.

Игровой текст хранится в одной из страниц рома. Чтобы узнать указатели на него, нужно узнать в какой регистр MPR загружается страница с этим текстом. Не буду вам многое объяснять, это тема для другой документации. Приведу лишь табличку, показывающую какое физическое адресное пространство, соответствует всем регистрам MPR. Это важно.
 
регистры
 
Теперь приведу в пример формулы для расчёта указателей. Она аналогична NES.

((Offset – 0x200)) and 0x1FFF) + 0xX000

  • 0xX000 - это значение зависит от того, в какой регистр записывается страница с нужным текстом. То есть здесь могут быть значения0x0000, 0x2000. 0x4000, 0x6000, 0x8000, 0xA000, 0xC000, 0xE000.
Чтобы узнать в какой регистр загружается текст, нужно немного освоить дебаг. Если для этого нет желания, то тогда можно воспользоваться методом банального перебора значений. Если найдёшь один указатель, то и найдёшь все остальные.
 
Neutopia
 
Теперь возьмём игру Neutopia (U). К примеру, первый блок текста начинается с адреса 0x31546 (Oh! You must be Jazeta! Thank heavens you came. A terrible tragedy has befallen us. Late last night и пр.). Стопбайтом является байт F2, байтом переноса строки –F1. Заголовок у ромов PCE равен 0x200 (512 байт). С помощью дебага я выяснил, что страница с текстом грузится во второй регистр MPR, т.е. MPR2. А так как MPR2 соответствует адресное пространство процессора от 0x4000 до 0x5FFF включительно, то смещением в формуле будет 0x4000:

(( 0x31546 – 0x200) and 0x1FFF) + 0x4000 = 0x5346

  • заголовок можно убрать, также как в случае со SNES, и тогда он будет не нужен нам в расчетах. Сделать это можно при помощи хексредактора

Меняем байты местами и получаем пару 46 53, которая и находится по адресу 0x31211.

Рассчитаем ещё один пойнтер на текст после стопбайта (Evil is everywhere now that Dirth has captured the и пр.), начинающийся с адреса 0x3188C.

((0x3188C – 0x200) and 0x1FFF) + 0x4000 = 0x568C

Последовательность 8C 56 находится по адресу 0x3135E. Пойнтеры в этой игре идут без фиксированного интервала и расположены немного выше текста. Лишь изредка интервал между ними равняется двум байтам.

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

По адресу 0xAE9E начинается текст игровых титров. Здесь байт переноса строки – FEFE, а стопбайт – FF. Страница с текстом загружается в регистр MPR5, а значит, смещением, относительно которого вычисляются указатели, будет 0xA000.

Рассчитываем указатель на этот адрес:

((0xAE9E – 0x200) and 0x1FFF) + 0xA000 = 0xAC9E

Полученное значение свопим, то меняем байты местами и ищем поиском. Данный указатель находится чуть выше текста по адресу0xAE7C. Это первый указатель в таблице. Далее без интервала следуют указатели на все остальные строки титров – B1 AC ((0xAEB1 – 0x200) and 0x1FFF) + 0x4000 = 0xACB1), CA AC ((0xAECA – 0x200) and 0x1FFF) + 0x4000 = 0xACCA) и так далее.

Game Boy / Game Boy Color


Указатели на GB/GBC такого же типа, как и на SNES, только из-за особенностей адресации приставки они всегда хранятся в промежутке памяти 0x0000-0x7FFF (две области 0x0000-0x3FFF и 0x4000-0x7FFF). Они могут быть абсолютные и относительные, 2-х или очень редко 3-х байтными. Весь ром делится на банки размером 0x4000 килобайт каждый. Чтобы узнать номер банка, в котором расположен текст, достаточно разделить адрес на 0x4000. Если указатель 3-х байтный, то номер банка является одним из байтов указателя. Второй и третий байт рассчитывается по формуле. Другими словами, для расчёта абсолютных указателей на эту платформу необходимо использовать следующие формулы:

Offset and 0x3FFF = 2-й и 1-й байты указателя
или
Offset and 0x3FFF + 0x4000 = 2-й и 1-й байты указателя,

Offset / 0x4000 = № банка = 3-й байт указателя (в случае, если указатели 3-х байтные).

Рассмотрим пример абсолютных 2-x байтных указателей в игре Astro Rabby (J) на черно-белый Game Boy. После завершения любого этапа появляется надпись «Stage n-n Clear», начинающаяся с адреса 0x44DD. Для её нормального перевода нам необходимо найти указатели. Если внимательно присмотреться, то станет понятно, что стопбайтом является байт 0xFF. И текст выводятся следующим образом, сначала идут два байта, указывающие на его координаты (x,y) на экране, а потом сам текст (FFxyStageFF). Пойнтер будет указывать именно на первый байт координат (x).
Указатели в данной игре загружаются в область памяти 0x4000-0x7FFF. Найдём указатель на слово Stage (0x44DD).
 
Astro Rabby

0x44DD and 0x3FFF + 0x4000 = 0x44DD

Наш указатель 0x44DD, а в роме он может храниться, как обычно в виде, последовательности DD 44 выше или ниже текста. Ищем нашу последовательность и находим DD 44 немного выше текста по адресу 0x43D5, а также по адресу 0x43BD. Это и есть наши указатели. При проверке выяснится, что один из них указывает на появление надписи Stage сразу после старта игры, а второй на эту же надпись, только после прохождения этапа.
Найдём указатель на следующее слово Clear по адресу 0x44E8. Как и в первом случае рассчитываем указатель, а затем ищем последовательность байтов E8 44. Она находится по адресу 0x43BF, то есть сразу же после одного из указателей на слово Stage. Таким образом, можно вычислить все указатели в этой игре.
Рассмотрим пример 2-х байтных абсолютных указателей. Теперь возьмём игру Revelations –The Demon Slayer (U) на цветной Game Boy Color.
 
Revelations –The Demon Slayer

Запустите игру и посмотрите заставку, появится первое диалоговое окно с текстом "This is your last Gaia training. Now, go to the Shrine". Этот текст начинается с адреса 0x3A573 и находится в большом текстовом блоке (0x382BC - 0x3B173) Байт переноса строки –0E, стопбайт – F3 FF или F3 F0. Рассчитаем указатель на этот адрес:

0x3A573 and 0x3FFF + 0x4000 = 0x6573

Нужная нам последовательность 73 65 находится выше текстового блока по адресу 0x3825A. Это самый простой пример хранения указателей в виде структурированной таблицы, где указатели идут без интервала друг за другом. Как видите, ничего сложного нет. Например, в игре Alien 3 (GB) точно такие же указатели находятся ниже текста и расположены с большим интервалом друг от друга.
 
Рассмотрим пример перекрёстных указателей игры Shantae (U) [C][!]. Основные текстовые блоки находятся по адресам 0xA0000-0xA3FFF, 0xA4000-0xA7FFF, 0xA8000-0xABFFF. На первый взгляд перевод не вызывает никаких проблем. До тех пор, пока вы не найдёте указатели. :)

Возьмём первый же текст, появляющийся после запуска игры What? Not another pirate attack! и находящийся по адресу 0xA0582 в роме. Стопбайтом является комбинация FF00.

sh_0 

Перед строкой идут два байта 84 45 (little endian), которые являются «внутренним» относительным пойнтером строки в массиве и указывают на первую букву строки без учёта указателя.  Смещение для расчёта этого указателя 0x9C000.

0xA0584 - 0x9C000 = 0x4584

sh_13


Помимо «внутреннего» имеется также и «внешний» трёхбайтный абсолютный указатель, который указывает на адрес «внутреннего» указателя. Произведём расчёт указателя на строку 0x0A0582:

0xA0582 and 0x3FFF + 0x4000 = 0x4582

 

0xA0582/ 0x4000 = 0x28

 

В роме указатель 0x824528 хранится по адресу 0x1D5E0.

Помимо этого, вперемешку с текстом идут и скрипты событий, указатели на которые также хранятся отдельно, а на названия локаций в городах имеются только абсолютные трёхбайтные указатели, которые хранятся в виде мини-таблицы прямо вперемешку с текстом диалогов. Только «терминатором» для строк является байт 0x80. Рассчитываются они по вышеуказанной формуле.

 

sh_2

sh_1

 

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


Sega Master System

Приставка использует процессор Z80. При этом 16-битные значения составляются из двух однобайтовых регистров. В результате этого значения указателей могут храниться как в Big-Endian, так и в Little-Endian. Что это значит, мы увидим чуть ниже. Хедер на платформе при расчете указателей не вычитается, тип указателей – двухбайтные относительные или абсолютные. Размер одного банка на платформе равен 0x4000, то есть 16 килобайт. Область для текста занимает в памяти пространство от 0x0000 или 0xBFFF (три области по 16 килобайт или 0x4000). Таким образом, рассчитать абсолютные указатели в зависимости от нахождения указателей в области памяти можно по формулам:
 
Ptr = Offset and 0x3FFF
или
Ptr = Offset and 0x3FFF + 0x4000
или
Ptr = Offset and 0x3FFF + 0x8000
 
Ознакомимся с указателями на эту платформу на примере игры King's Quest - Quest for the Crown. Стопбайт 7F, байт переноса 7E. С адреса 0х1C0BA (GOAT WITCH) идут названия предметов, врагов и команд в меню и пр.
 
sms
 
Текст загружается в область памяти 0x8000-0xBFFF, поэтому рассчитаем указатель:

Ptr = 0x1C0BA and 0x3FFF + 0x8000 = 0x80BA
 
В роме данный указатель хранится как последовательность BA 80 и находится по адресу 0x1C010. C этого адреса начинается таблица указателей на блок текста (0x1C0BA-0x1C2A4). Теперь найдем указатели на блок текста с описаниями локаций, в игре которые выводятся в рамках. Строка имеет следующую форму: {XYtext}, где X – ширина, а Y – высота рамки с текстом, а text – собственно текст. Поэтому указатель будет указывать на байт XЭтот блок расположен по адресам 0x1E94E  0x1FA63. Все указатели в данном случае относительны адресу начала большого блока с текстом игровых диалогов (0x1C65E). С адреса 0x1E989 начинается строка (9A03 You are standing outside a castle***).
 
sms
 
 
Ptr = 0x1E989 – 0x1C65E = 0x232B
 
В роме ищем последовательность 2B 23 и находим её по адресу 0x1C2A9. Это и есть наш указатель, левее находится последовательность F0 22 – это указатель на первую строку в малом блоке 0x1E94E – 0x1FA63 (0x1E94E – 0x1C65E = 0x22F0).Указатели на этот малый блок идут по порядку. В этом случае используется метод записи байтов Little-Endian, то есть от младшего к старшему.Теперь рассмотрим блок игровых диалогов (0x1C65E – 0x1E485). С ним не так всё просто, как кажется на первый взгляд. Указатели на строки в этом блоке также относительны его началу. Возьмём строку 9B02 You're in the deadlywaterначинающуюся с адреса 0x1D4EB
 
 
sms
 
Указатель на неё:

0
x
1D4EB – 0x1C65E = 0x0E8D 

Чтобы найти значение указателя в роме, не нужно менять местами байты. В данном случае используется метод хранения данных 
Big-Endian. Ищем 0E 8D выше текста. Данное значение встречается по адресам 0xC837, 0xCCAB, 0xCD4B, 0xDA48, 0xDD6E, 0xDD82, 0xDDEE, 0xE2F2 и 0xE471. Таким образом, мы видим, что указатель на эту строку встречается в роме 9 раз! После анализа игры было выявлено, что указатели на текстовый блок разбросаны и хранятся двумя методами. Древние компиляторы раньше и не такие чудеса творили.
 

Sega Game Gear
Указатели на этой приставке практически такие же, как и на Sega Master System. В роме могут храниться как в Little-Endian. В обеих консолях установлен процессор Z80. Для доступа к памяти используется страничная адресация. Размер одного банка в памяти равен 16 кб, то есть 0x4000. Банки в адресном пространстве рома начинаются с адреса 0x8000.

В играх применяются трехбайтные абсолютные указатели, вида XYZ, где Х - номер банка, УZ - смещение текста от начала банка. Как правило, байт номера банка идёт в коде отдельно от указателей и при должной сноровке и знании дебага на эту консоль, текст можно переместить куда угодно.

Формула для расчета младших частей указателей: 

Ptr = Offset and 0x3FFFF + 0x8000

Рассмотрим пример таких указателей, встреченных в игры Crystal Warriors (JUE) [!]. Стопбайт – FF, байт переноса строки – FA. С адреса 0хAF7F (IRIS) идёт блок с именами персонажей и названия магий в магазине/режиме битвы:

cw1

 Рассчитаем указатель:

Ptr = 0xAF7F and 0x3FFF + 0x8000 = 0xAF7F

Для двухбайтных адресов сам адрес и будет указателем. В роме необходимо искать последовательность 7F AF, то есть указатель записан в Little-Endian. Эта комбинация встречается по адресу 0xAEE1, а также с адресов 0xAF51 и по 0xAF60 в составе таблицы указателей (0xAEE1-0xAF7E). То есть один указатель повторяется ещё 9 раз. Для нормального перевода необходимо подключить их все.

В роме встречается также ещё одна вариация таких указателей. По адресу 0x168D8 находится строка BATTLE, появляющаяся в боевом меню.

cw2


Указатель на неё рассчитывается обычным образом:

Ptr = 0x168D and 0x3FFF + 0x8000 = 0xA8D8

В роме он расположен по адресу 0x1ED74. Интересно, что указателем на следующие три строки RETREAT (0x168E0), MONSTER (0x168E8) и SPELL (0x168F0) также является значение 0xA8D8, записанное в роме несколько раз по следующим адресам 0x1ED7F (RETREAT), 0x1ED8A (MONSTER) и 0x1ED95 (SPELL).  Только к каждому значению необходимо будет прибавить числа 0x00 (для строки BATTLE), 0x08 (для строки RETREAT), 0x10 (для строки MONSTER) и 0x18 (для строки SPELL) соответственно. Это стало ясно, потому что указатели идут в коде и после каждого из них идёт арифметическая операция добавления - ADD (машинный код 0x3E). Длина строки 8 байт (включая стопбайт), и она прибавляется к адресу первой строки.

cw3



Nintendo 64
 
Рассмотрим пример 4-х БАЙТНЫХ АБСОЛЮТНЫХ ПОЙНТЕРОВ из игры Paper Mario. После запуска игры мы видим следующее:
 
pp
 
Строка с данным текстом (Today…) начинается в роме Paper Mario (U) [!].z64 с адреса 0x1B830C4 и идёт в текстовом блоке0x1B830C0-0x1B8555F. Стопбайтом является сочетание FD, в начале строки идёт комбинация служебных байтов FF 29 9F. Смещением, относительно которого располагаются все строки, является адрес 0x1B83000. Найдём пойнтер на увиденное предложение. Берём адрес первого служебного байта в строке и отнимаем от него адрес, относительно которого располагаются строки:
 
0x1B830C4 – 0x1B83000 = 0xC4

Пойнтеры на N64 состоят их 4 байтов, то есть искать будем последовательность 00 00 00 C4. Такая последовательность встречается в роме очень много раз, поэтому для точного нахождения рассчитаем указатель на следующую строку 0x1B830D0 (I'm going to tell the story of"Star Spirits and Good Wishes).
 
0x1B830D0 – 0x1B83000 = 0xD0

Теперь нам известны два указателя. Вполне возможно они идут в виде структурированной таблицы, то есть друг за другом. Попробуем найти последовательность 00 00 00 C4 00 00 00 D0. Нам повезло, совпадение найдено по адресу 0x1B85564, а это сразу же после текстового блока! То есть если быть внимательным, то и без поиска можно было наткнуться на таблицу указателей, которая хранится по адресам 0x1B85560-0x1B856EF.
 
pp

Наверняка будет интересно узнать, как было найдено смещение строк (0x1B83000) для вычисления указателей. Дело в том, что по этому смещению идёт пойнтер 0x00002560, который указывает на адрес начала таблицы указателей для рассматриваемого нами блока(0x1B830C0-0x1B8555F):
 
pp
 
0x1B8300 + 0x00002560 = 0x1B85560

Следующий пойнтер 0x00006944 уже указывает на другую таблицу указателей на следующий за нашим текстовый блок (0x1B856F0-0x1B89943). Таким образом, текстовые строки в этой игре относительны адресу начала таблицы указателей на таблицы указателей для строк. Если посмотреть глобально, то ром на N64 представляет собой псевдоархив с файлами. Можно вырезать кусочек данных с указателями на указатели, блоками текста и указателями на текст и увидеть, что это вполне законченный и упорядоченный файл, в котором указатели на блоки и их адреса полностью соответствуют.  
( 13.09.2022 . )