Побочная история №5. Франкенштейн из скриптов, или Как научить RGG Engine читать длинный русский текст (Русификатор Yakuza 5)
Знаете это чувство? Ты полдня пишешь код, настраиваешь парсеры, нажимаешь «Собрать». Игра запускается, ты заходишь в меню — и всё работает идеально. Названия предметов влезают, описания стилей подробные, шрифт красивый, никаких вылетов. Ты откидываешься на спинку кресла, чувствуя себя гением хакинга. А потом запускаешь первую сюжетную катсцену, Дайго Доджима открывает рот сидя в такси, и... игра намертво схлопывается на рабочий стол.
Добро пожаловать в разработку русификатора Yakuza 5. Здесь каждая победа над движком оборачивается новым невидимым боссом.
Сегодня я хочу поделиться чисто технической изнанкой проекта и нашей новой "Энциклопедией методов". Главная головная боль любого перевода старых японских игр — это лимиты длины строк. Английские (и тем более японские) слова компактные. Если разработчики выделили под слово 4 байта памяти, вы не можете просто взять и вписать туда длинное русское слово. Вы заденете соседние данные, сместите структуру файла, и RGG Engine накажет вас вылетом.
Сегодня я покажу, как мой инструментарий эволюционировал от слепых hex-замен до умной перекомпиляции баз данных, и почему даже самые проверенные алгоритмы иногда дают сбой.
1. Анатомия проблемы: Улица с указателями
Чтобы понять мою боль, нужно понять, как игра читает текст. Представьте, что оригинальный файл игры — это длинная улица с домами. В домах лежат разные данные: текстуры, скрипты, настройки. В некоторых домах лежит текст. Где-то на этой улице стоят «знаки» (указатели), на которых написано: «Название предмета находится в доме № 5000».
Если я беру короткое английское слово и вписываю поверх него длинное русское, улица «растягивается». То, что было домом № 5000, уезжает вперед и становится домом № 6500. Но старый знак всё ещё показывает на 5000! Движок идет по знаку, попадает в пустоту или в кусок чужого скрипта — и игра закрывается с ошибкой.
Поэтому я создал свою «Техническую энциклопедию хакинга». В ней я фиксирую методы, как вставлять перевод и переписывать «знаки» так, чтобы игра ничего не заподозрила. Вот основной арсенал, который прошел боевое крещение в моих руках:
- Safe Append (Безопасное добавление): Моя основная «рабочая лошадка». Это далеко не самый идеальный метод: он капризный, с кучей подводных камней и часто выдает ошибки, которые приходится лечить вручную. Но это единственный универсальный вариант, который срабатывает почти везде, когда другие способы бессильны. Я вообще не трогаю оригинальную структуру "улицы", а дописываю весь новый русский текст в самый конец файла (в "кучу"), а затем с помощью своего скрипта прохожусь по всему файлу, нахожу старые указатели и меняю их адреса на новые, ведущие в мой район.
- RGG DB Recompile (Золотой стандарт): А вот это — мой фаворит. Когда я работаю с файлами, у которых есть сигнатура 20 07 03 19, я всегда выбираю этот метод. Он дает 100% стабильность, потому что не пытается "обмануть" движок, а пересобирает базу данных по правилам разработчиков. Но, к сожалению, он работает только с узким кругом файлов-баз данных.
- Table Reconstruction (Реконструкция таблиц): Если в конце файла есть служебная таблица с ID (маркеры 81 09 00...), я полностью удаляю её и генерирую заново, прописывая в ней новые правильные координаты для всех строк. Это чисто и надежно.
- Delta Shift (Хирургический сдвиг): Самый сложный метод. Если игра читает файл строго последовательно и не терпит "куч" в конце, я вставляю перевод прямо внутрь, попутно пересчитывая каждый байт в файле, чтобы "знаки" на улице не сбились. Это похоже на перекладку асфальта на действующей трассе — страшно, сложно, но иногда необходимо.
- Pointer Table Rewrite (Перезапись таблицы): Для файлов, которые состоят только из заголовка-таблицы и текста. Я просто пересчитываю всю таблицу заново.
- Timeline Splitting (Субтитры): Если длинная фраза не влезает в экран, я программно режу её на две части, пропорционально деля время показа.
Все эти методы — как разные инструменты в наборе архитектора. Некоторые файлы требуют "молотка" (Safe Append), а некоторые — "полной реконструкции здания" (RGG DB).
2. Непобедимый босс: Формат 20 07 03 19 (RGG DB)
В папке главного меню лежат самые важные файлы: item.bin (инвентарь), ability.bin (описания навыков) и caption.bin (подсказки).Я радостно натравил на них свои сканеры, попытался сдвинуть указатели и... игра зависла намертво.
Оказалось, что эти файлы — это не просто текст со знаками. Их первые 4 байта равны 20 07 03 19. Это означает, что передо мной не текстовый документ, а полноценная скомпилированная база данных (B-Tree/Hash Map).
Внутри нее данные организованы как корни и ветви дерева. Любая моя попытка изменить длину слова, дописать что-то в конец или сдвинуть хотя бы один байт разрушала сложнейшие хеш-таблицы. Игра просто не могла найти нужную "ветку" и падала. Стало казаться, что нормального перевода предметов я не увижу никогда.
3. Node.js спешит на помощь (Рождение RGG DB Studio)
Здесь на помощь пришло сообщество. Оказалось, что формат 20 07 03 19 был успешно разобран моддером SlowpokeVG. Его скрипты на Node.js доступны на GitHub, и они оказались именно тем ключом, который подходил к этой бинарной двери.
Я взял эти скрипты и интегрировал их прямо в свой хаб для перевода. Так родился новый плагин — RGG DB Studio.
Вместо того чтобы хирургически ковыряться в шестнадцатеричном коде бинарника, я теперь делаю так:
- Скрипт полностью декомпилирует базу данных в читаемый текстовый формат .json.
- В графическом интерфейсе своей RGG DB Studio я спокойно вписываю русский текст любой длины.
- Мой "Франкенштейн" кодирует русские буквы в Latin-1 (через подмену символов для кастомного шрифта) и отправляет это обратно в скрипт Node.js.
- Скрипт с нуля компилирует новый .bin файл! Он заново пересчитывает размеры всех блоков, перестраивает хеш-таблицы и выстраивает правильное дерево указателей по чертежам оригинальных разработчиков.
Никаких лимитов. Никаких вылетов.
4. Доказательство:
Игра ожила
Результат превзошел ожидания. Впервые я увидел инвентарь Кирю и его навыки на русском.
Названия предметов, описания, категории — всё влезает, всё читается. Ни одного "битого" символа. Я потратил кучу времени, чтобы подружить Python, Node.js и движок 2012 года, но результат того стоил.
5. Ложка дёгтя:
"Тот самый" вылет
Но в каждой побочной истории должен быть свой твист. Я уверовал в свои силы, зашел в сюжетный диалог с Дайго в такси и… игра вылетела.
Как выяснилось, мой скрипт для обработки .msg (файлов диалогов) оказался недостаточно точным. Эти файлы — не просто текст, а смесь текста и бинарных инструкций: таймингов камер, кодов анимаций и команд движка. Мой алгоритм поиска адресов текста оказался слишком "агрессивным" и в поисках указателей залез в святая святых — в бинарный код катсцены.
Это был мощный урок: универсальные методы сканирования хороши для баз данных, но для диалогов нужен отдельный подход с парсингом структуры. Сейчас я дорабатываю алгоритм, чтобы он учитывал границы текстового блока, и скоро вернусь к этим диалогам( Тут не в диалогах проблема. Разберусь переписать надо).
6. Великий кодировщик: как я заставил движок «читать» русский язык
Вы, наверное, заметили на моих скриншотах, что в hex-редакторе вместо привычных русских букв часто красуется какая-то абракадабра вроде ÀÁÂÃÄ. Это не ошибка и не баг — это результат работы моего «Кодировщика».
В чем была проблема:
Движок Yakuza 5 (наследие PS3-эпохи) ничего не знает о кириллице. Он заточен под специфическую однобайтовую кодировку. Если попытаться "вломиться" туда с UTF-8, игра мгновенно упадет, так как UTF-8 использует до 4 байт на символ, а движок ожидает жестко один байт.
Как я это решил:
Я написал свой кодировщик, который работает как переводчик между моими данными и игрой. Вместо того чтобы мучить движок Юникодом, я создал таблицу соответствий, где каждая русская буква «прячется» за символом из таблицы Latin-1.
- Буква «А» превращается в À
- Буква «Б» превращается в Á
- Буква «Я» превращается в ß
Почему это критически важно:
- Безопасность и стабильность: Это решение — не просто прихоть. Главная причина, по которой я использую такую подмену — защита от вылетов. Игра панически боится определенных байтов (например, 0xFF), принимая их за команды завершения строки или системные критические ошибки. Мой кодировщик "фильтрует" весь текст, не давая "опасным" байтам попасть в исполняемый код.
- Экономия места: Игра думает, что я использую обычные латинские символы. Она не видит разницы, и это позволяет мне вписывать длинные русские фразы в те же лимиты памяти, которые были выделены под короткие английские.
Я уже подробно разбирал этот технический ад с байтом 0xFF в одном из прошлых постов:
Итог:
Мой «Кодировщик» — это база, на которой строится вся работа с текстом. Внутри игры (в её памяти) это выглядит как "мусор", но для игрока на экране — это читаемый русский язык. Это был единственный способ "обмануть" старый движок, не переписывая его рендер с нуля.
Итоги работы
На данный момент настроен инструментарий для баз данных (RGG DB Studio). Удалось добиться стабильной сборки файлов инвентаря, навыков и других справочных данных. В работе остаются файлы диалогов (.msg) и скрипты катсцен — текущий метод сканирования требует доработки, так как затрагивает исполняемый код анимаций, что приводит к вылетам.
[UPD] Важное расследование: Я зря грешил на Дайго и катсцены! Как показал дальнейший дебаг методом исключения, ошибка крылась совершенно в другом месте — в файле деталей для такси (drive_parts.bin).
В выходные планирую доработать парсер для диалогов и заняться вопросом корректной обработки границ текстовых блоков. Тогда же подготовлю основной пост, где соберу всё воедино: покажу результат тестирования сборки и общие итоги.
Статус проекта:
- Методы обхода лимитов (Safe Append, Delta Shift, RGG DB) — Готово
- Разработка RGG DB Studio (Инвентарь, навыки, UI) — Готово
- OCR-сканер (Текстуры и подписи) — Готово
- Шрифт и кодировщик Latin-1 — Готово
- Сюжетные диалоги (.msg) — В разработке
- Катсцены — В разработке
📚 Хронологическая цепочка Хроник:
- Часть 1: Автоматизация — Читать
- [Side Story] Инструменты — Читать
- [Side Story] Инженерия Бешеного Пса — Читать
- Часть 2: Проблемы и решения — Читать
- Часть 3: Баг 0xFF — Читать
- [Side Story] Шрифт в 4К — Читать
- Часть 4: Навигатор Сузуки — Читать
- [Side Story] Студия Сайджо — Читать
- Часть 5: Лицо игры и изнанка кода — Читать
- [Side Story] Невидимый фронт — https://dtf.ru/games/4800892-rusifikator-yakuza-5-remastered-nevidimyj-front