logo

MTE и Отладка

Дорогой друг, сегодня ты узнаешь о том, как отладкой кода можно найти в играх Денди текст, упакованный МТЕ или DTE. Если, конечно, ты знаешь что такое хексредактор и для чего он нужен и осилишь эту доку до конца.
В первую очередь, наверняка, ты хочешь узнать, что же за зверь такой этот МТЕ. Алекс и King Mike в своих доках уже неплохо объяснили, что это такое, но я постараюсь сделать это чуть попроще. Конечно, для переводчика хорошо иметь простой и не пожатый текст в игре, которую он переводит. Но разработчику глубоко наплевать на чувства переводчика, и даже наоборот: ему главное выполнить поставленную перед ним задачу, в том числе и впихнуть все данные игры в ограниченное пространство. Во времена Денди с этим могли быть большие проблемы: лазерных дисков тогда ещё не было, а в картридже место не резиновое. Разработчики мечтали об эффективных методах сжатия, хотя бы того же текста, но, увы, Эффективные алгоритмы, которым ты радуешься каждый день в виде zip, rar или 7z архивов слишком громоздки и медлительны для скромных возможностей денди, поэтому максимум, на что мог рассчитывать разработчик - относительно простые и в то же время эффективные способы ужиматься, к которым и относится DTE.DTE - Dual Tile Encoding, несмотря на своё грозное название, довольно простой и не требующий много процессорного времени способ уменьшить объем, занимаемый текстом. Возьмем предложение “Вдобавок, наводчик восьмым чувством воспринял воздух вокруг, поднятый взрывом и вовремя упал возле воронки.” Здесь 107 знаков. Можно ли уменьшить его размер, чтобы в сохраненном текстовом файле он занимал поменьше места? Ну, например, вот так: “Вдоба#к, на#дчик #сьмым чувст#м #спринял #здух #круг, поднятый взры#м и #время упал #зле #ронки. #=во” Здесь уже 101 знак. И при желании даже человек сможет легко расшифровать то, что здесь написано, что уж говорить о машине.Разберемся подробнее, что же мы сделали. Мы взяли часто повторяющийся слог (на самом деле две соседних буквы) и заменили их знаком, который наверняка никогда не попадётся в тексте. А ещё мы написали нашу замену в конце, чтобы мы смогли расшифровать её потом. В играх Денди знак, который никогда не попадётся это любой шестнадцатеричный код, который не соответствует какой-нибудь букве алфавита (т.е. который не внесёшь в .tbl файл). А приписка в конце будет называться “словарем DTE”. Словарь, кстати, может содержать сколько угодно таких сочетаний, что повысит эффективность метода.
Или, к примеру, такое предложение: “В казавшемся заваленном заводе вдруг завизжал мотор, и завопили чайки, завидевшие заварушку.” - 91 знак. “В ка#шемся #аленном #оде вдруг #изжал мотор, и #опили чайки, #идевшие #арушку. #=зав” - 83 знака. Здесь ужатие уже сильнее, да оно и понятно. И такая схема, которая кодирует больше двух буков (да хоть всё слово с пробелами перед ним, например “или”, “но”) называется МТЕ - Multiple Tile Encoding и, как вы видите, не сильно отличается от DTE. А DTE - это вообще частный случай МТЕ. К слову сказать, в словаре МТЕ может содержаться много сочетаний или даже целых слов, как ты понимаешь, совершенно разной длины. Очень часто игры содержат в словаре MTE не части слов, а просто целые слова. Обычно, в таком случае говорят, что игра использует словарную систему, хотя фактически это всё тот же MTE.
Итак, приступим к интереснейшему процессу отладки кода какой-нибудь игры, использующей метод MTE. Чтобы перевести игру, мы, конечно, захотим найти текст, ужатый MTE, словарь MTE и указатели на текст, и слово в словаре. Чем мы и займёмся.Как правило, вся работа процессора с текстом сводится к следующему: читаются буквы по порядку и выводятся на экран. Если встречается неизвестная буква (её хекс-код больше какого-то определенного значения), сразу же после этой буквы будет идти номер слога в словаре MTE. Отсчитывается от начала словаря этот номер, считывается слово и выводится на экран сразу все буквы этого слова, а потом происходит дальнейшее чтение обычного текста. Но бывает и так, что эта “неизвестная буква” одна - уже и есть сылка на слово из словаря. Просто от её значения нужно отнять определённое другое значение, и получится индекс слова в MTE-словаре.
Сам процесс вывода буквы на экран до безобразия прост. В самом начале нужно указать видеопроцессору (на Денди его называют PPU) откуда нужно начинать выводить первую букву. Это делается записью двух байт в порт $2006. Эти два байта - 16-битное значение адреса в видеопамяти приставки, куда и нужно будет вписать текст. Ну а потом записывается номер буквы (чаще всего он совпадает с хекс-кодом буквы из составляемого тобой .tbl файла) в порт $2007, и вуаля - буква на экране! PPU переходит к следующему адресу в видеопамяти автоматически, поэтому в дальнейшем можно просто все время писать в порт $2007.
Теперь нам просто нужно найти какой командой вписывается в видеопамять номер буквы искомого слова, и проследить, откуда вообще взялся этот номер. В конечном итоге мы должны будем придти к адресу в ROM файле.
Проделать все эти нелегкие операции нам поможет проверенный помощник - FCEUXD SP. Прекрасный эмулятор с потрясающим встроенным отладчиком, трейсером, просмотрщиками и другими классными фичами.
В качестве жертвы мне пришло в голову выбрать ROM “Adventures in the Magic Kingdom (U).nes” Итак, откроем ROM в FCEUXD SP и дойдем до экрана выбора имени. Сохранимся (F5) и перейдем к первому текстовому экрану “HEY, GOOFY! IT’S TIME FOR THE BIG PARADE!”. Найдём адрес в ROM’е слова Goofy.
Откроем просмотрщик тайловых карт, которые сейчас находятся в видеопамяти (именно сюда будут записываться буквы искомого слова) -> Tools ->NameTable viewer… И подведем мышку к букве ‘G’ слова GOOFY. Выбирай всегда те слова, которые расположены как можно выше и левее - они имеют меньшее значение адреса в видеопамяти. Нижние или правые отображения, что ты можешь увидеть - всего лишь точные копии (зеркала) основной области видеопамяти. Интересующая нас запись номера буквы будет производиться именно в основную область видеопамяти, а не в зеркала.
1MTE
Как видно из строки ‘PPU Address’ адрес нашей буквы ‘G’ в видеопамяти равен $2269 (не забудьте подвести курсор к букве в окне NameTable viewer). Обратите внимание на строку Tile ID:10 это номер нашей буквы G. Кстати, он совпадает с хекс-кодом буквы G в таблице, как чаще всего и бывает. Но откуда взялся этот номер? Почему именно $10, а не $80 или D4?ОткройPPUViewer(Ctrl + F1)ипосмотривправуючасть − этонашшрифт, которыйможнонайтивтайловомредакторе.Каждыйтайлздесьпронумерованивкаждойтаблицеграфики(вДендиееназываютPatternTable)можетсодержатьсядо256(FF) тайлов. И так вышло, что тайл буквы расположен шестнадцатым, т.е. имеет номер $10.И чтобы вывести эту букву на экран (т.е. записать ее номер в видеопамять) нам нужно всего лишь сохранить её в порт $2007. Всё что теперь требуется от переводчика - это отследить эту запись и посмотреть, откуда в этот порт записывается номер $10 и откуда он вообще берётся.
Для этого нам понадобится поставить точку останова на запись в ячейку видеопамяти (а именно $2269). Точка останова, она же брейк, она же бряк, она же брейкпоинт и так далее, это просто условие, при котором наш отладчик остановит выполнение кода игры, и мы сможем внимательно посмотреть на команду, которая этот останов вызвала. В нашем отладчике остановы могут быть на запись и чтение из ячеек видеопамяти или просто памяти и на выполнение команды по определенному адресу. Разберем нужную нам точку останова на практике. Прежде всего, открой отладчик (F1) и нажми на кнопку ‘Add…’
2MTE
Думаю, особо здесь ничего объяснять не стоит, это точка останова на запись в ячейку по адресу $2269 видеопамяти. После нажатия на OK останов появится в списке действующих точек останова, о чем будет свидетельствовать буква E(enabled) перед обозначением точки останова.
Что ж, понятно, что когда экран уже нарисован, отлавливать запись нашей буквы не имеет смысла, вот почему, если ты не забыл, мы сохранились перед загрузкой нашего экрана. Пришло время загрузить сохранение (F9) и нажать на старт. Как видишь, экран потемнел, а вскоре появилось окно нашего отладчика со сработавшей точкой останова.
3MTE

$C3C8:8D 07 20 STA $2007 = #$00
Что это? А это наша команда записи в ячейку видеопамяти. Хотел найти её - получи. $C3C8 - это адрес данной команды в памяти приставки, 8D 07 20 - это байты машинного кода, которые, в сущности, и есть наша команда, STA $2007 - это мнемоническая запись команды, чтобы человек хоть как-то мог работать с кодом, а не с какими-то байтами. Команда обозначает STore Accumulator, а $2007 называется операндом команды и является портом, через который производится запись в ячейку видеопамяти.
= #$00 - это значение, которое до записи было в данной ячейкеРазумеется, эта дока не посвящена вопросам языка ассемблера 6502, но некоторые команды, которые будут нам встречаться, я по мере сил, постараюсь пояснить. Итак, самые азы приставочных внутренностей - это регистры. В Денди их три: A(Accumulator), X, Y. В них может находиться какой-нибудь байт. Если вы хотите записать из одной ячейки памяти в другую, то вам придется загрузить содержимое ячейки в регистр (обычно это Аккумулятор), а только потом сохранить ее в нужную нам ячейку из Аккумулятора. Команда STA - это запись значения аккумулятора в какую-нибудь ячейку.Посмотрите на Окошко ‘PPU:’ там будет адрес ячейки, к которой сейчас производится доступ. Как мы и хотели, это $2269.
Что же касается содержимого Аккумулятора, то его также легко увидеть в окне отладчика - это $24.
Помнится, нам нужна была запись номера $10, а не $24… В чем же дело? А в том, что перед тем как записывать новый экран нужно стереть старый. Это обычная практика программистов, потому что так надежнее и вообще, это хороший тон. А под номером $24, между прочим, находится пустой тайл, так что мы пока что нашли всего лишь команду очистки памяти от содержимого предыдущего экрана.
Итак, возобновим выполнение нашей программы, но перед этим зайдем в Trace Logger (Tools -> Trace Logger…), выберем Log to File и пропишем сам файл, в который теперь будут записываться все команды, выполняемые процессором. Не забудь нажать Start Logging. Перейдём к отладчику и нажмем Run.
Опять команда очистки экрана (судя по содержимому Аккумулятора). Ничего, опять возобновим выполнение кода. Хм, будем нажимать Run, пока не натолкнемся на такую же команду записи, но с цифрой $10 в Аккумуляторе. `$CF0D:8D 07 20 STA $2007 = #$00`
Вот и наша команда! Нажмем Step Into, чтобы она выполнилась и записалась в файл лога.Понятно, теперь мы видим, где происходит запись, но чтобы увидеть, откуда взялось в аккумуляторе число $10, нам было бы хорошо запустить процесс выполнения кода в обратной последовательности. К сожалению, такой функции в нашем отладчике нет, поэтому мы и вели запись всех команд в отдельный файл. Прекратим запись команд, нажав в Trace Logger кнопку Stop Logging. Теперь нужно открыть получившийся огромный текстовый файл и изучить его. Я пользуюсь просмотрщиком из Total Commander - работает очень быстро даже с гигантскими файлами (сейчас у меня этот лог примерно 41 мегабайт и сомневаюсь, что его откроет блокнот). После открытия файла нужно переместиться в самый его конец и вот что мы увидим:

$CF0A:BD 00 04 LDA $0400,X @ $0446 = #$10 A:01 X:46 Y:01 P:nvUbdIzc
$CF0D:8D 07 20 STA $2007 = #$00 A:10 X:46 Y:01 P:nvUbdIzc

Во-первых, о формате записей в файле лога: ну самое начало, я уже объяснял и, надеюсь, оно понятно.
A:10 X:46 Y:01 - это значения, находящиеся во всех трех регистрах (ведь в логе мы не можем заглянуть в окно отладчика)
P:nvUbdIzc - это флаги статуса процессора. n - negative - флаг отрицательного результата, а C - Carry - флаг переноса. Остальные для тебя мало что будут значить.
Итак, к самому коду. LDA $0400,X @ $0446 = #$10 - команда загрузки(LoaD Accumulator) из ячейки $446(в ней сейчас находится число $10) в аккумулятор. Здесь X - это смещение от начала области памяти $400. Теперь чтобы прочитать следующее значение из этой области памяти достаточно увеличить X на единичку и закрутить все в цикл, чтобы прочитать какой-нибудь большой массив байт.Так значит, чтение производится из $446, и мы нашли текст? Не совсем. Вот схема распределения памяти приставки:

+---------+-------+-------+-------------+
| Address | Size  | Flags | Description |
+---------+-------+-------+-------------+
| $0000   | $800  |       | RAM         |
| $0800   | $800  | M     | RAM         |
| $1000   | $800  | M     | RAM         |
| $1800   | $800  | M     | RAM         |
| $2000   | 8     |       | Registers   |
| $2008   | $1FF8 | R     | Registers   |
| $4000   | $20   |       | Registers   |
| $4020   | $1FDF |       | Expansn ROM |
| $6000   | $2000 |       | SRAM        |
| $8000   | $4000 |       | PRG-ROM     |
| $C000   | $4000 |       | PRG-ROM     |
+---------+-------+-------+-------------+

Как видим, 446 - это адрес в области RAM (random access memory) - оперативная память, а это значит, что эту $10 туда кто-то тоже записал. Нас же интересует чтение из области PRG-ROM (т.е. непосредственно ROM’а), а значит адрес, откуда эта десятка читается должен быть не меньше $8000.Что ж, посмотрим, кто же записал в область оперативной памяти номер нашей буквы G. Для этого найдем сочетание 0446 выше команд, которые мы только что рассматривали.

$D012:B9 00 03 LDA $0300,Y @ $0347 = #$10 A:01 X:46 Y:47 P:nVUbdIzC
$D015:10 03 BPL $D01A A:10 X:46 Y:47 P:nVUbdIzC
$D01A:9D 00 04 STA $0400,X @ $0446 = #$24 A:10 X:46 Y:47 P:nVUbdIzC

LDA $0300,Y @ $0347 = #$10 Не повезло. Опять чтение из оперативной памяти. Поступим как и в прошлый раз, но теперь в окне поиска введем 0347.

$CDB8:B1 F3 LDA ($F3),Y @ $BCF0 = #$10 A:BC X:47 Y:00 P:nvUbdIZc
$CDBA:C9 FF CMP #$FF A:10 X:47 Y:00 P:nvUbdIzc
$CDBC:F0 07 BEQ $CDC5 A:10 X:47 Y:00 P:nvUbdIzc
$CDBE:C8 INY A:10 X:47 Y:00 P:nvUbdIzc
$CDBF:20 C8 CD JSR $CDC8 A:10 X:47 Y:01 P:nvUbdIzc
$CDC8:9D 00 03 STA $0300,X @ $0347 = #$24 A:10 X:47 Y:01 P:nvUbdIzc

LDA ($F3),Y @ $BCF0 = #$10 Вот то, что мы искали! Согласно распределению памяти, это область ROM’a, а значит, наши поиски завершены. Откроем в эмуляторе хексредактор (F6) и перейдем по адресу $BCF0, чтобы посмотреть, что же это за буква и что ее окружает… Что за чертовщина! Одни нули. Куда же делось пресловутое $BCF0 = #$10? На самом деле, хексредактор показывает содержимое памяти приставки именно в момент, когда мы остановили процесс выполнения игрового кода. А от момента, когда мы выполнили команду $CDB8:B1 F3 LDA ($F3),Y @ $BCF0 = #$10 до останова прошла уйма времени. За это время игра вполне могла переключить банки. Я не буду здесь вдаваться в подробности процесса переключения банков - очень многое я когда-то описал в своих предыдущих документах. Что нам нужно, так это открыть хексредактор именно в момент выполнения команды $CDB8:B1 F3 LDA ($F3),Y @ $BCF0. В этом нам опять же поможет отладчик. Ставим останов на выполнение этой команды:
4MTE
И жмем Run. После того, как выполнение будет остановлено, переходим к хексредактору и видим нашу букву G. Процесс поиска практически подошел к концу. Мы узнали адрес буквы в банке уже загруженном в память приставки. Чтобы узнать адрес буквы в ROM мы можем нажать правой кнопкой на байте $10 и нажать ‘Go Here In ROM File’ и вуаля - вот он наш адрес: FD00.
Однако, всё же, что это за место такое? Что окружает наше слово GOOFY и вообще, может, здесь только одна буква G и всё? Чтобы ответить на этот вопрос, нужно составить обычную таблицу в формате .tbl пользуясь PPU Viewer’ом. Т.е. если мы знаем, что 10 = G, то у нас уже есть готовая таблица, нужно только немножко поработать в блокноте. Загрузив таблицу в наш хексредактор (File -> Load *.TBL File), получим такую картину:
5MTE
Батюшки! Да это же словарь MTE! Легко увидеть, что каждое слово оканчивается байтом $FF. Теперь становится понятно, что GOOFY - словарное слово и его загружают из словаря. Теперь стоит рассмотреть поподробнее процедуру загрузки слова:

$CDB8:B1 F3 LDA ($F3),Y @ $BCF0 = #$10 A:BC X:47 Y:00 P:nvUbdIZc

Что такое LDA ($F3),Y и почему это обозначает загрузку из $BCF0? На самом деле, как я уже не раз рассказывал в своих предыдущих документах это особая команда загрузки. Она подразумевает, что в ячейке оперативной памяти по адресу $F3 лежит младший байт адреса ячейки, откуда надо загружать (в нашем случае это младший байт word’а BCF0, а это $F0), а по адресу $F4 будет лежать старший байт (в нашем случае, это $BC), ну а в Y будет смещение от этого адреса. Так вот, по адресам $F3 и $F4 сейчас лежит указатель на слово GOOFY в словаре МТЕ. Нам нужно лишь отследить, кто и откуда этот указатель туда записал. Давай введем в поиске значение 00F3 и поищем его вверх от нашего последнего места в трейс логе:

$CDAC:B9 00 BB LDA $BB00,Y @ $BB2D = #$F0 A:00 X:47 Y:2D P:nvUbdIZc
$CDAF:85 F3 STA $00F3 = #$02 A:F0 X:47 Y:2D P:NvUbdIzc
$CDB1:B9 80 BB LDA $BB80,Y @ $BBAD = #$BC A:F0 X:47 Y:2D P:NvUbdIzc
$CDB4:85 F4 STA $00F4 = #$41 A:BC X:47 Y:2D P:NvUbdIzc

Мда, далеко ходить не надо. Ну что ж, вот и адрес наших поинтеров, в ROM’е это будет $FB10 и $FB90 в первой таблице лежат младшие байты указателей, а во второй - старшие. `$CDAC:B9 00 BB LDA $BB00,Y @ $BB2D = #$F0 A:00 X:47 Y:2D` - нужно сказать, что здесь в Y находится номер слова в словаре MTE.
Как нам теперь искать сам текст? Естественно, в нем должен содержаться номер слова, а как мы выяснили, он был записан где-то в Y. Проследим немного вверх запись в Y:

$CDA2:B1 51 LDA ($51),Y @ $A169 = #$2D A:47 X:47 Y:0B P:nvUbdIzc
$CDA4:A8 TAY A:2D X:47 Y:0B P:nvUbdIzc
$CDA5:E6 53 INC $0053 = #$0B A:2D X:47 Y:2D P:nvUbdIzc
$CDA7:A9 03 LDA #$03 A:2D X:47 Y:2D P:nvUbdIzc
$CDA9:20 99 C4 JSR $C499 A:03 X:47 Y:2D P:nvUbdIzc
$C499:85 44 STA $0044 = #$01 A:03 X:47 Y:2D P:nvUbdIzc
$C49B:85 ED STA $00ED = #$01 A:03 X:47 Y:2D P:nvUbdIzc
$C49D:A9 01 LDA #$01 A:03 X:47 Y:2D P:nvUbdIzc
$C49F:85 EB STA $00EB = #$00 A:01 X:47 Y:2D P:nvUbdIzc
$C4A1:A5 ED LDA $00ED = #$03 A:01 X:47 Y:2D P:nvUbdIzc
$C4A3:20 E7 C4 JSR $C4E7 A:03 X:47 Y:2D P:nvUbdIzc
$C4E7:8D F9 FF STA $FFF9 = #$43 A:03 X:47 Y:2D P:nvUbdIzc
$C4EA:4A LSR A:03 X:47 Y:2D P:nvUbdIzc
$C4EB:8D F9 FF STA $FFF9 = #$43 A:01 X:47 Y:2D P:nvUbdIzC
$C4EE:4A LSR A:01 X:47 Y:2D P:nvUbdIzC
$C4EF:8D F9 FF STA $FFF9 = #$43 A:00 X:47 Y:2D P:nvUbdIZC
$C4F2:4A LSR A:00 X:47 Y:2D P:nvUbdIZC
$C4F3:8D F9 FF STA $FFF9 = #$43 A:00 X:47 Y:2D P:nvUbdIZc
$C4F6:4A LSR A:00 X:47 Y:2D P:nvUbdIZc
$C4F7:8D F9 FF STA $FFF9 = #$43 A:00 X:47 Y:2D P:nvUbdIZc
$C4FA:60 RTS A:00 X:47 Y:2D P:nvUbdIZc
$C4A6:A5 46 LDA $0046 = #$00 A:00 X:47 Y:2D P:nvUbdIZc
$C4A8:F0 07 BEQ $C4B1 A:00 X:47 Y:2D P:nvUbdIZc
$C4B1:A9 00 LDA #$00 A:00 X:47 Y:2D P:nvUbdIZc
$C4B3:85 EB STA $00EB = #$01 A:00 X:47 Y:2D P:nvUbdIZc
$C4B5:60 RTS A:00 X:47 Y:2D P:nvUbdIZc
$CDAC:B9 00 BB LDA $BB00,Y @ $BB2D = #$F0 A:00 X:47 Y:2D P:nvUbdIZc
.......

TAY - это перевод из Аккумулятора в Y (Transfer A to Y)Опять поглядим, что же находится по адресу $A169. Хм, опять не тот банк. Не беда - поставим останов на выполнение команды по адресу $CDA2, и после остановки вновь посмотрим на хексредактор.
6MTE
Бородки-Сковородки! Да ведь это же основной текст. И расположен он по адресу $6179 в ROM файле.
Внимательно приглядевшись к нему, можно легко различить, что слово GOOFY заменено в тексте двумя байтами F6 и 2D. Ну, что касается $2D, мы уже знаем, что это номер слова в словаре MTE. А вот F6, очевидно, контрольный байт, обозначающий, что нужно выводить именно слово MTE. Возможно, в этой игре полно других контрольных байт, возможно, даже есть еще какая-нибудь схема, типа DTE или второго MTE, но, в любом случае, моя задача показать в общем как такую схему можно расколоть.
Итак, нам осталось лишь найти указатели на строку “HEY, GOOFY! IT’S TIME FOR THE BIG PARADE!” Раньше мы нашли команду $CDA2:B1 51 LDA ($51),Y @ $A169 = #$2D A:47 X:47 Y:0B - здесь в Y - номер буквы в нашей строке, а в ячейках $51 и $52 - указатель на всю строку. посмотрим, кто же записывает туда наш указатель, введя в окне поиска назад ‘0051’:

$CC1A:B1 F3 LDA ($F3),Y @ $9A20 = #$5E A:9A X:00 Y:00 P:NvUbdIzc
$CC1C:85 51 STA $0051 = #$21 A:5E X:00 Y:00 P:nvUbdIzc
$CC1E:A9 00 LDA #$00 A:5E X:00 Y:00 P:nvUbdIzc
$CC20:18 CLC A:00 X:00 Y:00 P:nvUbdIZc
$CC21:65 14 ADC $0014 = #$20 A:00 X:00 Y:00 P:nvUbdIZc
$CC23:85 F3 STA $00F3 = #$20 A:20 X:00 Y:00 P:nvUbdIzc
$CC25:A9 9C LDA #$9C A:20 X:00 Y:00 P:nvUbdIzc
$CC27:65 15 ADC $0015 = #$00 A:9C X:00 Y:00 P:NvUbdIzc
$CC29:85 F4 STA $00F4 = #$9A A:9C X:00 Y:00 P:NvUbdIzc
$CC2B:B1 F3 LDA ($F3),Y @ $9C20 = #$A1 A:9C X:00 Y:00 P:NvUbdIzc
$CC2D:85 52 STA $0052 = #$DC A:A1 X:00 Y:00 P:NvUbdIzc

Аналогично находя ячейки $9a20 и $9c20 в ROM, получаем адреса таблиц младших ($5a30) и старших($5с30) байтов указателей.
Итак, теперь у нас есть все данные для того, чтобы приступить к переводу игры (на эту игру даже не замахивайтесь, Chief_exb вас уже опередил). Разумеется, можно выдирать и вставлять текст руками, но надежнее использовать уже созданные для этого утилиты (я лично пробовал только на Kruptar). Особую сложность представляет у начинающих составить таблицу МТЕ или DTE под уже переведенный скрипт. Часто они сделаны настолько неоптимально (т.е. включают редко встречающиеся слоги или слова), что скрипт не влезает в отведенное под него место. Тут стоит заметить, что для таких вещей также существует ряд программ (например, Martial или WordCount).