Трудности перевода Yakuza 5: Как взлом шрифтов и переход на 1-байт помогли мне избавить игру от вылетов и выпустить рабочую Альфу 0.3
В последний раз я подавал признаки жизни 24 апреля. Почти месяц полной тишины в эфире.
Если кто-то думал, что я забросил перевод Yakuza 5 — нет. Я просто медленно сходил с ума, пытаясь подружить капризный японский движок 2012 года с русской кириллицей. И вот на днях на моём Boosty наконец-то вышло обновление: Билд 0.3 Alpha.
Под капотом этой сборки скрывается самый сложный и выматывающий технический этап за всё время работы — переход на однобайтовую кодировку (Latin-1).
Зачем мне вообще понадобился этот безумный костыль? UTF-8, конечно, использовать было можно. Но на практике это превратилось в ад: пришлось бы переписывать кодирование под каждый отдельный формат файлов в игре. К тому же сам движок Yakuza 5 выделяет под текстовые строки катастрофически мало оперативной памяти. Длинный русский текст в UTF-8 просто взрывал буферы.
В итоге однобайтовый хак (где 1 русская буква весит строго 1 байт) стал единственным спасением. Он дал мне суперсилу — использовать метод In-place (запись перевода прямо поверх оригинальных байт) в тех сложных системных файлах, структуру которых я физически не мог расковырять. Например, там, где было невозможно найти или безопасно перенаправить указатели. Это банально в разы безопаснее, стабильнее для движка, да и файлы весят меньше (хотя размер тут дело десятое).
Но этот месяц работы был по-настоящему мучительным. Я переписывал свой парсер сотни раз, крутясь по кругу и ломая голову над одними и теми же багами. Самым невыносимым было то, что реплика выводилась на экран на чистом русском, но игра наотрез отказывалась показывать кнопку перехода к следующему диалогу. Из-за этого зависал весь файл сценария, а если это был сюжетный квест — намертво вешалась вся игра. Это — главный минус и главная боль однобайтовой кодировки, из-за которой мне пришлось пережить тонну рутины.
В итоге я прошёл полный круг и вернулся практически к той логике парсера, с которой начинал, но только максимально вычищенной и оптимизированной. Но самое главное — всё это наконец-то стабильно(почти) работает!
Ниже я во всех подробностях разберу всю эту бинарную кухню: как именно перекраивался шрифт, по какому принципу велась замена символов и почему мне пришлось переписать половину собственного переводческого инструментария.
⚙ Анатомия текста в Yakuza: почему стандартные методы не работают
Для начала пара слов о том, как движок Yakuza 5 вообще работает с текстом. В любом бинарном файле игры (будь то диалоги .msg или базы данных меню .bin) строки хранятся в виде блоков байтов, на которые ссылаются указатели (адреса) из заголовка файла.
При переводе такой структуры у переводчика есть два пути:
- In-place (Запись на месте): Оригинальные английские буквы затираются русскими. Это самый безопасный метод, потому что структура файла и адреса в заголовке остаются абсолютно нетронутыми. Но перевод здесь жёстко ограничен длиной английского оригинала.
- Append (Запись в конец): Если перевод длиннее оригинала, строка дописывается в самый конец файла (в свободную зону), а в заголовке разыскивается указатель и перенаправляется на этот новый адрес.
И вот тут стандартная кодировка UTF-8, которая используется в 99% современных русификаторов, превращается в мину замедленного действия.
Дело в том, что в UTF-8 английские буквы весят по 1 байту, а русская кириллица — по 2 байта (а иногда и по 3 с форматированием).
Простой пример: слово Save (4 буквы — 5 байт с нуль-терминатором в конце). На русском это Сохранение. В UTF-8 это слово сожрёт 21 байт.Уместить 21 байт на место 5 байт методом In-place физически невозможно. Приходится делать Append — переносить строку в конец файла и обновлять указатели в заголовке.
Но в Yakuza 5 есть десятки файлов баз данных, меню и специфических скриптов, структуру заголовков которых расковырять до конца невероятно сложно. Найти там все скрытые указатели, которые ссылаются на оригинальные текстовые блоки — адский труд. Если пропустить хотя бы один указатель, игра попытается прочитать текст по старому адресу, наткнётся на пустоту или кашу из байтов и намертво вылетит.
Именно здесь на сцену выходит однобайтовая кодировка (Latin-1).
В Latin-1 слово Сохранение весит ровно 11 байт (вместо 21 в UTF-8!). Благодаря этому колоссальное количество фраз влезает методом In-place прямо на место оригинала. Мне больше не нужно искать скрытые указатели в сложных бинарниках — я просто перезаписываю русский текст поверх английского, и движок игры съедает его без единого вылета. А там, где длина всё-таки превышена и приходится делать Append, файлы весят в два раза меньше, что экономит драгоценную память консольного движка
🎨 Камуро-Дизайн: мой автоматический шрифтовой конвейер
Просто заменить кодировку в Python-скриптах — это только половина дела. Как заставить саму игру рисовать русские буквы вместо французских умляутов или немецких символов вроде ß? Ведь если я пишу перевод в Latin-1, игра по умолчанию попытается нарисовать символ À вместо нашей А.
Разгадка крылась в текстуре шрифта игры — файле sd_hankaku.dds. Это обычная двухмерная картинка, на которой ровной сеткой расположены все системные символы.
Сначала я залез в графический редактор GIMP, просто чтобы поэкспериментировать. Настроив там точную пиксельную сетку, я обнаружил важную штуку: буквы идеально вписываются в ячейки 16x16. Но поскольку я совершенно не умею пользоваться всеми этими сложными профессиональными редакторами и рисовать каждую букву вручную — я пошёл по пути программиста.
Я полностью переписал и модернизировал собственный плагин «Камуро-Дизайн» (85_kamuro_design.py), превратив его в полноценный автоматический шрифтовой конвейер.
Теперь мне не нужно ничего рисовать руками. По нажатию всего одной кнопки мой плагин сам считывает все подмены из утилит text_utils.py, берёт выбранный векторный TTF/OTF шрифт, применяет к нему тонкие геометрические настройки (сжатие ширины, растяжение высоты, отступы и смещения по осям X/Y) и автоматически за долю секунды отрисовывает всю русскую кириллицу прямо в DDS-текстуру игры! Я немного поигрался с расположением букв, настройкой сжатия и смещениями, и в итоге шрифт стал выглядеть практически идеально — буквы больше не накладываются друг на друга и отлично читаются в диалогах.
Для визуального контроля всей этой бинарной сетки я создал интерактивную HTML-карту kamuro_map.html (она лежит по пути 04_ANALYSIS_REPORTS/docs/kamuro_map.html). Это мой личный интерактивный атлас шрифта, где можно открыть страницу в браузере, навести курсор на любую ячейку и мгновенно увидеть её HEX-адрес и символ подмены.
Конечно, не обошлось без странных бинарных аномалий, которые потрепали мне нервы. В обычных рядах символов всё шло ровно и соответствовало стандарту Latin-1. Но в рядах A и B (диапазон байтов 0xA0 - 0xBF) обнаружился непонятный сдвиг ровно на 1 байт в сторону. Из-за этого символы уходили на одну ячейку левее или правее, ломая маппинг (например, Иена, которая по стандарту должна быть на 0xA5, вела себя непредсказуемо, улетая на 0xA6). Я так до конца и не понял, было ли это ошибкой стандартов, хитрой задумкой движка или косяком оригинальных японских авторов текстуры, но этот сдвиг в один байт стал ещё одной неприятной загадкой, которую мне пришлось решать при настройке авто-отрисовки в плагине.
Для обложки статьи на DTF я, скорее всего, выберу один из своих свежих внутриигровых скриншотов — генерировать какую-то особую картинку мне сейчас банально лень, да и настоящие кадры из игры лучше всего показывают реальное положение дел.
А показать мне есть что, ведь у этого сложного перехода на 1-байтовую кодировку есть свои весомые плюсы, суровые минусы и компромиссы, на которые мне пришлось пойти ради стабильности.
➕ Плюсы: скорость и железный In-place
Главный триумф этой сборки — масштабная статистика компиляции по всей игре. Мой новый компилятор успешно обработал 5 214 бинарных файлов и внедрил в игру 81 025 строк перевода (на основе базы из 68 209 уникальных строк словаря)!
При этом более 70 200 строк сели по своим оригинальным адресам (In-place) без изменения структуры файлов, а опасных наложений указателей в Append-зоне по всем пяти тысячам файлов — строго ноль. Вся игра теперь полностью пересобирается с нуля примерно за 14 минут , хотя раньше на один только кривой проход уходило до 4 часов.
Разработка шрифта тоже перестала быть ночным кошмаром. Мне ещё предстоит найти системный файл игры, в котором прописаны точные межсимвольные интервалы и отступы (кернинг), но даже без этих настроек мой шрифт Камуро-Дизайн выглядит на экране отлично, буквы не накладываются друг на друга и прекрасно читаются в диалогах.
➖ Минусы и компромиссы: визуальные жертвы ради стабильности
Самая заметная проблема — это наложение символов на системные знаки. Поскольку кириллица заняла ячейки оригинальных ASCII-символов, игра выводит русские буквы там, где раньше стояли знаки.
И здесь мы натыкаемся на самую забавную историческую бинарную аномалию с байтом 0x5C, которая потрепала мне немало нервов:
- Теория (ASCII / Latin-1): В стандартной международной таблице символов под кодом 0x5C всегда находился обратный слеш \. Хаб в коде видит этот байт именно так.
- Практика (Shift-JIS): В японских кодировках (JIS X 0201) разработчики ещё в 80-х годах принудительно заменили обратный слеш \ на знак Иены ¥ на том же самом байте 0x5C. С тех пор на всех японских компьютерах и в играх любой обратный слеш в коде движок игры автоматически читает и рисует как Иену. Именно поэтому художники SEGA на оригинальной текстуре sd_hankaku.dds в ячейке 0x5C нарисовали Иену, а не слеш (как видно на моём втором скриншоте).
- Что пошло не так: При создании русского шрифта ячейка 0x5C была единственным логичным местом, куда я мог посадить русскую букву у. Но системный код игры (например, счётчик денег в углу экрана) по-прежнему выводит оригинальный байт 0x5C для обозначения валюты. В итоге на экране вместо Иены рисуется буква у (например, у30,000 в деньгах).
Похожая беда случилась и с другими системными символами:
- Русская ы заняла место дефиса - (0x2D). Из-за этого даты сохранений теперь пишутся как 2026ы05ы20.
- Русская т заняла место плюс-символа + (0x2B).
Почему я оставил это так? Потому что если попытаться запустить автозамену байтов 0x5C (слеш) или 0x2D (дефис) по всем системным бинарникам игры, программа неизбежно затрёт важные структурные смещения в заголовках файлов и намертво сломает игру. Оставить временные буквы вместо знаков оказалось единственным безопасным решением для первой альфа-версии.
Кроме того, этот месяц работы был по-настоящему мучительным. Мне пришлось переписать свой парсер сотни раз, крутясь по кругу и ломая голову над одними и теми же багами. Самым невыносимым было то, что реплика выводилась на экран на чистом русском, но игра наотрез отказывалась показывать кнопку перехода к следующему диалогу (кнопки [A] или [E]). Из-за этого зависал весь файл сценария, а если это был сюжетный квест — намертво вешалась вся игра. Это — главный минус и главная боль однобайтовой кодировки, из-за которой мне пришлось пережить тонну рутины.
Ради стабильности мне также пришлось пойти на жёсткие косметические жертвы:
- Я полностью вырезал все теги цвета и курсива из словаря диалогов. Сейчас весь текст идёт стандартным сплошным цветом. Это сугубо декоративный момент — как только я буду уверен в стабильности на 100%, я смогу легко вернуть выделения обратно, но сейчас рисковать лишними ошибками не имеет смысла.
- Я не стал возвращать кавычки-ёлочки. Они тоже потенциально могли сломать парсер, и я просто не стал рисковать.
- Пришлось спасать теги действий (<Action:...>): Без специальной защиты в кодере эти теги кодировались в кириллицу и выводились на экран в виде гигантского текста на пол-экрана вместо красивых круглых кнопок геймпада.
Самый главный минус — на этой кодировке все старые переведённые плагины и системные .bin файлы больше не подходят, их придётся пересобирать заново под новую кодовую страницу.
Но если искать в этом положительные стороны: в тех старых файлах и базах данных и без того хватало скрытых багов. Этот вынужденный старт с чистого листа позволит мне заново перепроверить, вычистить и пересобрать все ресурсы игры на моих новых, гораздо более стабильных и безопасных инструментах.
🎬 Заключение и что дальше
Подводя итог этой безумной трёхнедельной эпопее, я могу сказать одно: технический барьер, который долгое время казался непреодолимым, наконец-то взломан. Переход на однобайтовую кодировку дался мне тяжело, но теперь у меня на руках есть стабильный, гибкий и невероятно быстрый инструментарий, который позволяет собирать файлы за минуты, а не за часы.
Альфа 0.3 — это только начало новой, стабильной главы перевода Yakuza 5. Впереди ещё много работы: нужно пересобрать базы данных .bin, дочистить мелкие меню, вернуть красивые цветные теги в диалоги и, наконец, найти тот самый конфигурационный файл разметки шрифтов, чтобы настроить идеальные межсимвольные отступы. Но теперь я точно знаю, что это лишь вопрос времени, а не технических ограничений движка.
Если вы хотите протестировать свежую сборку, помочь мне с поиском скрытых вылетов или просто следить за разработкой в реальном времени — добро пожаловать на мои ресурсы:
- Скачать Альфу 0.3 (v2) на Boosty: https://boosty.to/sungoro/posts/8adf0d92-4148-44be-956d-fda37fb68ae8
- Следить за оперативными новостями в Телеграм-канале: [https://t.me/yakuza5hub]
Огромное спасибо всем, кто поддерживает меня на Бусти и верит в этот русификатор. Ваша поддержка — это то, что заставляет меня продолжать копаться в бинарных недрах игры даже тогда, когда хочется всё бросить.
Играйте в хорошие игры, не бойтесь безумных технических костылей и до встречи в новых девлогах!