Гонки без зависаний, автосборка релизов и статус v0.3 Alpha: новый отчёт о переводе Yakuza 5 Remastered
Я продолжаю работать над переводом Yakuza 5 Remastered и делиться технической изнанкой этого процесса.
В моей прошлой публикации я подробно расписывал свои эксперименты с обходом лимитов длины строк через переход на однобайтовую кодировку. На первый взгляд казалось, что идея с манипуляцией байтами увенчалась успехом. Но суровая практика внесла свои коррективы: во время длительных игровых тестов этот эксперимент себя не оправдал. Из-за капризов движка посыпались нестыковки в других участках памяти, и мне пришлось принять решение — отложить эту затею и совершить полный откат назад на двухбайтовую UTF-8 кодировку.
Но этот откат не был напрасным. Во-первых, благодаря экспериментам с перерисовкой и подменой букв в шрифте, мне удалось значительно улучшить отображение символов. Русский шрифт в v0.3 теперь выглядит на порядок чище, ровнее и приятнее глазу, чем во всех ранних ревизиях. Во-вторых, этот опыт подтолкнул меня к более глубокому исследованию структуры файлов .msg.
Сегодня я рад представить вам большой отчёт о текущем состоянии версии v0.3 Alpha [GD1]. В этом лонгриде я подробно разберу:
- Как я победил зависание скриптовых анимаций в гонках такси Кирю. Я потратил несколько дней на непрерывную отладку структуры диалогов и параллельно разработал умный гибридный компилятор (который пишет короткие строки поверх оригинала, а длинные уводит в Append). Однако на практике оказалось, что реальной причиной вылетов была сама длина строки — даже если движок целиком её выводил, слишком длинный текст ломал тайминги скрипта. В итоге анимации заработали как швейцарские часы, когда я просто вручную сократил перевод этой проблемной фразы, хотя гибридный метод сборки всё равно остался в качестве отличного предохранителя.
- Как я закрываю старые хвосты: история о том, как по наводке из Telegram-чата от игрока EnniX я провёл ревизию Sushi Zanmai и вернул на место пропавшего в версии 0.12 «Отборного морского угря» (Choice Saltwater Eel).
- Новый Менеджер Релизов — плагин Хаба, который полностью избавил меня от ручной упаковки ZIP-архивов и генерации сопроводительных файлов, застраховав от случайных ошибок при сборке.
- Пару слов о «Дате-теке» — глобальной базе отслеживания строк, которую я сейчас временно отключил, чтобы не мешала в рабочих спринтах, но её концепт уже полностью готов.
Я предлагаю отправиться во внутренности старого движка Sega и посмотреть, как это устроено.
Раздел 1. Анатомия файлов .msg и в чём была истинная проблема гонок Кирю
Прежде чем говорить о гонках, нужно понять, как устроен типичный диалоговый файл .msg в Yakuza 5. По сути, это бинарный контейнер, состоящий из трёх основных частей:
- Таблица указателей (Header): Сетка 4-байтовых адресов (Big Endian), которые ссылаются на текстовые строки и их блоки метаданных.
- Блоки метаданных (Metadata): Наборы свойств для каждой реплики. Самые важные для нас команды — это print_len (визуальная длина строки в символах, маркер 02 00 00 14) и total_len (размер буфера в байтах, маркер 01 01 00 00). Для некоторых реплик также прописывается внутристроковая пауза (02 00 00 0A), которая делит фразу на части при выводе.
- Текстовые блоки: Оригинальные английские строки, перед которыми часто стоят технические префиксы — например, маркеры цвета или иконки кнопок (байт 0x13).
Когда я писал парсер, я заложил в него следующий принцип работы: он проходит по файлу, считывает все валидные указатели, отсекает служебные префиксы в начале строк, извлекает чистый текст и сопоставляет его с метаданными.
Долгое время я считал, что анимация в гонках такси Кирю ломается исключительно из-за того, как именно пересобирается файл. Я думал, что если писать строки в конец файла (метод Append), то игра неверно интерпретирует новые адреса или ломаются смещения. Параллельно с поиском причин я разрабатывал сложную гибридную систему сборки: если переведённая строка по байтам влезает в оригинальное место, она пишется прямо поверх оригинала (In-place), а если не влезает — уходит в конец (Append) с обновлением всех указателей и метаданных.
Однако реальность оказалась намного прозаичнее, а движок игры — капризнее.
В процессе тестов и отладки конкретных файлов (в частности, uid010c0887.msg) выяснилось, что решающую роль играет сама длина строки, а не метод её записи. Скрипт гонки имеет жесткие внутренние тайминги. Даже если парсер идеально рассчитывал метаданные и движок игры успешно выводил длинную русскую фразу на экран целиком, её визуальная длина просто ломала таймлайн сцены. Скрипт не успевал отработать анимацию, игра теряла синхронизацию, и сцена намертво зависала.
Решением стала обычная сценарная редактура. Как только я вручную сократил перевод проблемной реплики в скобках, сделав его лаконичным, анимация в гонке заработала как швейцарские часы. Гибридный парсер, над которым я работал в это же время, в итоге не стал панацеей конкретно для этой проблемы, но послужил мощным и безопасным фундаментом для всей остальной сборки.
Раздел 2. Как устроен гибридный парсер: статистика на живых цифрах
Чтобы показать, как гибридный компилятор справляется с обработкой тысяч файлов, я предлагаю взглянуть на логи реальной сборки на разных этапах тестирования.
Этап 1. Маленькая папка (первые успешные In‑place)
Здесь я собирал небольшую группу файлов и зафиксировал первые строки, которые смогли уместиться в старые рамки:
Файлов успешно: 203/203
Пересобрано файлов (были переводы): 6
Переводов применено: 69
In‑place: 3
Append: 66
Указателей обновлено: 66
Метаданных обновлено: 179
Время: 0.4 сек
Что это значит: Всего 3 строки из 69 подошли под метод In-place. Обратите внимание, что метаданных обновилось меньше, чем раньше (было 183). Это подтверждает, что для In-place строк мы больше вообще не трогаем оригинальные метаданные, сохраняя их нетронутыми в целях безопасности.
Этап 2. Папка средней величины (сотни файлов)
На этом тесте уже виден результат ручной полировки и сокращения опечаток:
Файлов успешно: 268/268
Пересобрано файлов (были переводы): 163
Переводов применено: 1627
In‑place: 123
Append: 1504
Указателей обновлено: 1504
Метаданных обновлено: 3696
Время: 0.9 сек
Что это значит: Уже 123 строки пошли по безопасному пути In-place — значит, укороченные переводы действительно начали идеально помещаться в оригинальные буферы, не раздувая файл.
Этап 3. Крупная папка (почти 3000 файлов)
А вот статистика по одной из самых массивных сюжетных папок:
Файлов успешно: 2959/2959
Пересобрано файлов (были переводы): 983
Переводов применено: 30605
In‑place: 2883
Append: 27722
Указателей обновлено: 27722
Метаданных обновлено: 105608
Время: 9.6 сек
Что это значит:
- Почти 10% всех переводов (2883 строки) записаны методом In-place.
- Число обновлённых указателей (27 722) ровно совпадает с числом Append-строк. Это идеальный маркер: каждая длинная строка имела ровно по одному указателю, сборка прошла без аномалий.
- Число обновлённых метаданных (105 608) примерно в 3.8 раза больше, чем число Append-строк. Это абсолютно логично, ведь для каждого Append мы пересчитываем print_len, total_len и, если есть, позицию паузы.
Этап 4. самая большая папка по диалогам
Файлов успешно: 1784/1784
Пересобрано файлов (были переводы): 1586
Переводов применено: 48860
In‑place: 2728
Append: 46132
Указателей обновлено: 46134
Метаданных обновлено: 154775
Время: 12.5 сек
Что это значит: Здесь мы видим небольшую математическую аномалию: указателей обновлено 46 134, в то время как Append-строк было 46 132 (разница в 2 указателя). Это нормальная ситуация для движка Якудзы: она означает, что две строки используются игрой повторно в нескольких местах, и парсер успешно обновил оба указателя, ссылающихся на один текстовый блок.
Раздел 3. Оптимизация скорости и оставшиеся обрезки
Собрать почти 3000 файлов и применить под 50 тысяч переводов всего за 12.5 секунд — это невероятно быстро. Такой скорости удалось достичь благодаря двум вещам:
- Многопоточность: Обработка распределяется по всем ядрам процессора через пул потоков ThreadPoolExecutor.
- Умный кэш: Парсер не сканирует бинарную структуру каждого .msg заново при каждой сборке. Один раз генерируется быстрый JSON-кэш в temp_json, из которого берутся адреса и метаданные. При этом оригинальный размер блока в байтах (orig_byte_len) всегда пересчитывается в реальном времени напрямую из бинарного файла, чтобы кэш случайно не подсунул устаревшие данные, если структура оригинала поменялась.
Остались ли в игре обрезки текста?
Да, частично они всё ещё встречаются в редких местах (особенно в побочных квестах или диалогах подземелий). Движок крайне капризен к длине фраз. Но теперь, когда у меня есть отлаженный снайперский дебаг, эти проблемы решаются очень просто: я нахожу нужную строку в логе и либо сокращаю русский перевод, либо, если это безопасно для анимации, даю парсеру увести её в Append. Работа над шлифовкой этих хвостов будет продолжаться.
Раздел 4. Оружейная комната Хаба: Менеджер Релизов и спящая «Дате-тека»
Когда работаешь с проектом такого масштаба в одиночку, автоматизация рутины — это не роскошь, а единственный способ выжить и не сойти с ума. Специально для сборки дистрибутивов я разработал Менеджер Релизов.
В прошлых версиях изменение свойств архивов (имя ZIP, описание) происходило «на лету» по мере ввода текста. На бумаге это выглядело удобно, но в реальности Tkinter капризничал: фокус ввода терялся, символы пропадали, и в итоге одна из сборок у меня просто собралась с битым именем.
Я решил эту проблему радикально. Я полностью убрал нестабильное автосохранение при вводе и добавил большую, надёжную зелёную кнопку «Применить и сохранить изменения». Теперь я могу спокойно отредактировать все свойства выбранного архива, применить их одним кликом, зафиксировать изменения в файле настроек release_settings.json и пересобрать все 13ZIP-пакетов без какого-либо риска получить повреждённый архив.
Ещё один важный ассистент в моей экосистеме — плагин «Дате-тека».
Это полноценная база данных на SQLite, которая работает как сыщик. Её задача — фиксировать каждую переведённую строку, отслеживать, какой именно плагин и файл её использует, вычислять точную разницу в байтах (size_delta), ловить дубликаты и подсвечивать конфликты переводов.
На текущем этапе, во время жестких спринтов по отладке гонок, я временно отключил «Дате-теку», чтобы её фоновые процессы не мешали мне быстро пересобирать файлы. Но сама архитектура и интерфейс полностью готовы, и в будущем я планирую доработать её вместе с менеджером архивов, превратив в идеального автоматического аудитора.
Раздел 5. Огромное спасибо Алексею: текстуры Victory Road и пиксель-перфект спрайты
Локализация игры — это далеко не только текст в диалогах. Это сотни текстур, которые нужно перерисовать так, чтобы они выглядели «как родные». И здесь неоценимую помощь мне оказывает тестер Алексей.
Я не совру, если скажу, что к текущему моменту он перерисовал уже около половины всех текстур в игре.Специально для версии 0.3 он проделал огромную работу:
- Полностью подготовил графику для боевого турнира Victory Road («Путь к победе»). Все плашки меню, результаты боёв и заставки теперь полностью на русском языке.
- Создал пиксель-перфект спрайты контекстных действий и кнопок (файл sprite_en.par). Все маркеры вроде «Открыть», «Говорить», «Взять» теперь отрисованы ровно, аккуратно, имеют строго один размер и идеально ложатся в оригинальный интерфейс.
Без его помощи визуальная часть v0.3 Alpha не была бы такой цельной. Огромное спасибо за этот титанический труд!
🎁 Бонусный мем-трек для тех, кто дочитал до конца
Для Кирю такси — это не просто прикрытие, это состояние души. И когда на горизонте появляется очередной наглый соперник, а из динамиков начинает играть ЭТО...
(Включаем воображение и поём на мотив известного шедевра):
🎵 Гонщик нелегальный...🎵 Едет на такси!🎵 Скорость на пределе...🎵 Везёт ананас по шоссе!🎵 Гонщик нелегальный...🎵 Нет тебе пощады!🎵 Кирю на коробке передач...🎵 Дрифтует у Нагасугая!
Спасибо всем, кто поддерживает проект на Бусти и следит за разработкой. Ваша поддержка помогает мне не опускать руки даже тогда, когда движок игры подкидывает очередную неразрешимую задачу.
📢 Телеграм-канал: https://t.me/yakuza5hub — Главный хаб новостей и обновлений перевода.
💬 Группа обсуждения (Чат): — Общение, обсуждение перевода и серии Якудза.
🧡 Поддержка на Boosty: https://boosty.to/sungoro — Подписка, ранний доступ к билдам и эксклюзивные материалы.
💸 Разовые донаты: https://pay.cloudtips.ru/p/6096c166 — Прямая поддержка автора через CloudTips.
🤖 Бот обратной связи: https://t.me/Cyber_Purgatory_bot — Бот для баг-репортов, логов и связи с разработчиком.
📝 Блог на DTF: https://dtf.ru/id3235977 — Технические статьи, отчеты и лонгриды по разбору игры.
До встречи в Камурочо! 🐉🔥