struct_diff сравнивает классы по составу и показывает, каких не хватает.
Но он отвечает на вопрос «того же ли набора методов», а не «тот ли код».
Оба вопроса приходится задавать: прыжки и звуки шагов были сломаны именно
кодом, который компилировался без ошибок и состоял из правильных методов.
javap_diff сравнивает инструкции построчно, вызывая javap на обоих деревьях
классификации. Наивное сравнение текста здесь бесполезно: перекомпиляция
перенумеровывает constant pool, поэтому почти каждая строка отличается
номером #123, хотя код совпадает. Инструмент убирает номера пула, сдвиги
смещений от ldc/ldc_w и форму инструкций, после чего остаётся реальная
разница.
На звуке инструмент показал, что все аудио-классы совпадают с оригиналом.
Остатки объясняются версией компилятора, а не правками: javac 8 собирает
конкатенацию строк через StringBuilder, javac 17 через invokedynamic, а
$values() у enum появился в более новых версиях.
ogg_duration читает частоту дискретизации из заголовка Vorbis и число
отсчётов из granule position последней страницы OggS. Отсюда видно, что файл
сам по себе короче или что его просто обрывают при воспроизведении, —
вопрос, который иначе приходится решать на слух.
Проверено, что все 1085 ogg совпадают с индексом по SHA-1.
Клиент запрашивал звук, движок его принимал и запускал, а на выходе было
тихо. Причина оказалась не в коде игры.
Minecraft 1.12.2 не умеет читать ассеты по индексу и объектному хранилищу.
Он ждёт обычное дерево: <assetsDir>/minecraft/<путь>. А лаунчер хранит
ассеты по-современному — indexes/1.12.json со ссылками на objects/<aa>/<sha1>.
Мы передавали лаунчеру корень этого хранилища как --assetsDir. Игре это не
выглядит как поломка: язык и текстуры берутся из самого jar, поэтому клиент
выглядит полностью рабочим. Но звуки с 1.7.3 лежат только в ассетах, и их
просто не было на диске. Движок поднимался, play() вызывался, ogg не
находился, а paulscode на это молчит.
- materialize_assets.py разворачивает дерево из индекса и хранилища,
жёсткими ссылками где возможно, и идемпотентен
- run-game.ps1 материализует ассеты перед запуском и указывает клиенту на
развёрнутое дерево; --assetIndex больше не нужен
- проверено: 1305 объектов, из них 1085 ogg, sounds.json на месте, ошибок
загрузки в логе нет
Заодно убрана временная диагностика звука и движения, добавленная ради
поиска причины: она доказала, что запрос и воспроизведение доходят до
OpenAL, и свою задачу выполнила.
Полный prepare-game.ps1 после этого проходит целиком: 112 патчей,
FORK COMPILES CLEAN, 3268 классов, RsxDebug в дереве не осталось.
Проект начинается с двух вещей: клиент, который мы действительно собрали, и
загрузчик, который является его частью, а не чем-то рядом.
Форк
- prepare-game.ps1 пересобирает клиент целиком: ванильный jar, SRG и MCP,
SpecialSource, FernFlower, патчи, автофиксеры, javac. Идемпотентен.
- Свой код игры лежит в src-overlay, потому что src пересоздаётся при каждой
сборке и всё написанное вручную там погибает.
- 116 патчей в patches/decompiler-fixes.txt. Каждый сверялся с оригинальным
байткодом через javap: компиляция не ловит ошибки декомпиляции.
- struct_diff.py сравнивает исходный байткод с нашей сборкой по классам,
интерфейсам, суперклассам, методам и полям, и отдельно проверяет, что
пропавшие классы действительно никому не нужны.
Загрузчик
- Собирается и тестируется без игры: build.ps1 не использует classpath
вообще, поэтому SelfTest из 128 проверок проходит без запущенного форка.
- Клиентский API не содержит ни одного игрового типа, реализацию даёт игра.
- Политика: мод меняет то, что клиент показывает, но никогда то, что истинно
в мире. Мир, предметы, игроки и сеть принадлежат серверу.
- Capability приведены в соответствие с этой политикой: осталось шесть, и
за каждым стоит реальная проверка. Прежние WORLD, PLAYER, NETWORK, TEXT,
INPUT и COMMAND выдавались, но ничего не проверяли.
- Команды модов имеют область CLIENT, SERVER или BOTH. Серверные не
выполняются на клиенте никогда.
- Свой jar загрузчика лежит в classpath игры и сборки форка.
Инструменты
- add_patch.py, чтобы каждое исправление переживало передекомпиляцию
- find_self_comparisons.py, find_remove_index_bugs.py, find_callers.py
- stage_run.py и fetch_missing.py для запуска клиента