Разбор шрифтов и перевод квеста 1996 г. — I Have no Mouth, and I Must Scream

в 13:53, , рубрики: data mining, reverse engineering, Восстановление данных, игры, квест про квест, перевод, реверс-инжиниринг, метки: , , , , ,

Всем добра!

Основанная на одноименной новелле Харлана Эллисона (Harlan Ellison) игра I Have No Mouth, and I Must Scream является одним из самых мрачных квестов всех времен. Давящая атмосфера не отпускает до самой развязки.

Недалекое будущее. Три сверхдержавы, США, Россия и Китай, каждая стремясь превзойти соперниц, создали суперкомпьютеры для ведения войн. Но они просчитались. Объединившись в единое целое, называющее себя AM, три суперкомпьютера, использовав мощь, данную им людьми, стерли человечество с лица земли. В живых компьютер оставляет только пятерых, которым предстоит послужить ему игрушками для бесконечных пыток.

В прошлый раз я описывал 8-битный шрифт, а в этот раз удалось разобрать 1-битный.
Оба варианта шрифтов не зашифрованы и не сжаты, это сильно упростило задачу.

Инструменты: IDA, dosbox + debugger, winhex, GBS.

КДПВ
image

Проблема — найти шрифт среди файлов.
Явно, типа FONTS, файлов и папок нет, есть ресурсный файл scream.res размером в 75,5 МБ.
С помощью Process Monitor (ex FileMon) с фильтром на dosbox составил таблицу порядка обращений к файлам, смещения и размеры.
Сперва начал смотреть .res файл с помощью GBS и портить блоки в scream.res по порядку их вызова, нашел место хранения изображений шрифтов:
image
Разбор шрифтов и перевод квеста 1996 г. — I Have no Mouth, and I Must Scream

Перебором набросал свои шрифты, взятые кажется из Arial, не подогнал ширину и выравнивание, но появилась уверенность, что игру можно перевести, коды букв, соответствовали cp1251.
Для рисования символов вполне сгодится и Эксель:
Разбор шрифтов и перевод квеста 1996 г. — I Have no Mouth, and I Must Scream

image

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

Шрифт 1-битовый, то есть одним байтом можно задать 8 пикселей. Несколько байт образуют ряд. Несколько рядов образуют высоту, получаем блок в памяти (высота * длину ряда) байт или прямоугольник разрешением (высота X (ряд * 8)).
Визуально в памяти и в игре выглядят так (зеленые и красные полоски показывают байт):
image
image

В игре используется маленький R3 шрифт и диалоговый R1.

Структура каждого шрифта

Заголовок — 1286 байт, состоит из
ITE_FONTHEADER, 6 байт
INT16, c_height — максимальная высота символа,
INT16 c_width — максимальная ширина символа, параметр совершенно ненужный,
INT16 row_length — длина ряда изображения шрифта (в байтах),

Затем 256 байт, массив индексов, сами индексы я приравнял к cp1251 (АЯая — это индексы 192-255).
INT16 index[256] — номер байта, с которого начинается изображение символа.

Затем 256 байт, массив ширин символов:
BYTE width[256] — ширина символа в пикселях, может быть любой в разумных пределах, от 0 до бесконечности. Ну до 255 :)

Затем 256 байт, флаг. Насколько я понял из серии экспериментов, он задает левый отступ, в пикселях.
BYTE flag[256] Unknown character flag (either 0 or 1)

Затем 256 байт — трекинг, сколько пикселей символа отрисовать. Отсчет слева-направо от 0 до бесконечности. До байта.
BYTE tracking[256] — трекинг

Следом за заголовком, идет блок данных (row_length * c_height) байт длиной.

Формула доступа к любому началу ряда:
font_data + (row_length * y),
где font_data — указатель начала данных шрифта, y — номер ряда.

Каждый символ занимает ((width[index] — 1) / 8) + 1 байт.

После этих данных художник форума выдал красивый, похожий на оригинал диалоговый шрифт. В оригинале было зарезервировано всякими значками и умляутами достаточно байт, чтобы уместить оригинальный и русский шрифт. В запасе в итоге даже остался 1 байт, который я занулил. Написал программку копирования блоков из BMP, где был нарисован русский шрифт в ряд, прямо в блок игры.
На выходе получил cp1251 набор символов, значки, цифры, английские и русские символы:
Разбор шрифтов и перевод квеста 1996 г. — I Have no Mouth, and I Must Scream

Приступил ко второму шрифту:
Разбор шрифтов и перевод квеста 1996 г. — I Have no Mouth, and I Must Scream

Вот тут случился косяк. В оригинальном блоке было очень и очень мало места, то есть из предложенного набора можно было исключить 14 дурацких символов, но этого явно не хватало для 66 русских букв.

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

Тут я заморочился как dos заголовок выкинуть и вынуть LE файл, который можно засунуть в IDA, та его радостно распознает как 32 битный LE файл. Открыл файл в winhex нашел строку "*** NULL assignment detected", пошел вверх до MZ и начало вырезал до MZ.
Спасибо cracklab.

Затем вылезла другая проблема, как сопоставить адрес в dosbox (выглядит как 180:200c61) и адрес в IDA (выглядит как cseg01:0001AC1F), чтобы поглядеть где выполняется код и сделать его анализ. На сайте SCUMMVM предложен довольно дурацкий вариант, рабочий, но крайне медленный. Я всё в ожидании IDADOS, когда можно будет код смотреть в IDA и сразу его трассировать мощью dosbox. Но пока… в IDA задаем поиском последовательность из примерно 10 байт, того, что видим в dosbox debuger.
Для этой игры получилась формула:
адрес в dosbox — 1DFFFE h = адрес в IDA.

К этому моменту мне были известны точные смещения начал блоков шрифтов и их размеры. Начал искать эти числа в IDA простым поиском, но ничего не нашел.
Затем в dosbox debuger задал прерывание на позиционирование указателя на чтение (int 21h, ah=42h, LSEEK). Перед вызовом в CX:DX должно быть значение, на сколько передвинуть указатель: (CX * 65536) + DX.

Через несколько прыжков нашел 2 подряд идущих вызова читать 2 шрифта из игры, как раз те, что используются.
В DX было нужное мне число. Отмотал немного назад и нашелся блок инициализации шрифтов, выглядящий примерно вот так:
mov eax, 6 ( в итоге пропатчил на mov eax, 3)
call InitFont
mov eax, 8
call InitFont

как раз в блоке — это 5 и 7 шрифт. Поиграв разными числами, поглядел как неиспользуемые шрифты выглядят в игре:
image
image
image
image

Посмотрел размеры блоков шрифтов, выбрал самый большой, перенес туда блок маленького шрифта, пропатчил исполняемый файл, сделал из BMP импорт русских букв (у bmp 256 цветов как раз один байт равен одному пикселю, приведение байта в биты дело второе), поправил индексы, трекинг, ширины, длину ряда, увеличил на единицу высоту, чтобы буквы сверху и снизу не сливались и наконец-то получил итоговый шрифт.
Сохранил оригинальные символы, цифры, буквы, добавлен ряд русских букв, включая Ёё.
image

Перфекционист во мне ликует.

Тема на old-games.ru.

Автор: jack7277

Источник



https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js