Skip to content

Repository files navigation

Rust Frida License Sacred Community

English · Deutsch

Sacred Communication Protocol

Хост, который связывает Sacred Gold с модами на Java. Этот репозиторий нужен тем, кто работает над самим загрузчиком. Для написания мода он не нужен.

Sacred Gold — 32-битная игра, а JVM загрузчика 64-битная, поэтому в одном процессе они не уживаются. protocol.exe ждёт запуска игры, внедряет в неё через Frida JavaScript-агент из coderpack и запускает JVM в отдельном java.exe. Затем он передаёт сообщения в обе стороны.

Агент общается с хостом сообщениями Frida. Хост общается с JVM текстовыми кадрами, по одной строке UTF-8 на кадр, через два именованных канала: <base>.in в сторону JVM и <base>.out обратно. Напрямую агент с JVM не говорит. Формат кадров описан в docs/PROTOCOL.md.

JavaScript агента встроен в protocol.exe. Хост собирает внедряемый скрипт в памяти и ничего не пишет ни в папку игры, ни куда-либо ещё. Хуки живут только в памяти игры и исчезают вместе с ней.

Как начать

Обычно хост запускает лаунчер. Запустить его можно и вручную из установленной папки:

<Sacred Gold>\launcher\protocol.exe

Пути хост находит рядом со своим исполняемым файлом, так что аргументы не нужны. Если игра запущена от администратора, запустите хост так же. Иначе Frida не подключится, а хост будет ждать, хотя и видит процесс.

Java хост ищет в таком порядке: java/bin/java.exe рядом с собой (туда лаунчер распаковывает скачанный JDK), %JAVA_HOME%\bin\java.exe, затем java из PATH. Обычно лаунчер передаёт свою Java через --java. Для настоящего запуска нужен JDK 21 или новее.

Флаг Что делает
--enable <ids> Загружает моды с этими идентификаторами через запятую
--java <путь> Использует указанный исполняемый файл Java
--dist <путь> Берёт JAR-файлы из этой папки
--mods <путь> Берёт моды из этой папки
--agent <путь> Читает агента из папки вместо встроенного, удобно при его правке
--hooks Печатает точки хуков в JSON и завершается

С неизвестным аргументом хост завершается с ошибкой.

Кадры

Вот фрагмент сессии:

EVT 2 hero.captured class=9 className=Daemon level=142 hp=27127 maxHp=27127
ASK 3 health.damage entity=player damage=553 current=19849 next=19296 max=26999
END 3 set.next=19849

EVT сообщает о событии и ответа не ждёт. ASK приходит от хука перед записью значения, и поток игры стоит, пока не получит ответ. END и есть этот ответ: он разрешает запись, отменяет её или подменяет поля. Здесь set.next=19849 оставляет прежнее здоровье, и урон не проходит.

Мод может и сам отправить команду:

CMD 1 player.gold
RES 1 ok=1 gold=104233

BYE завершает сессию. В ключах и значениях процентами кодируются только символы, которые сломали бы кадр: %, пробел, =, перевод строки и возврат каретки. Остальное остаётся как есть, поэтому живую сессию можно читать прямо в терминале.

Правила для ASK

ASK держит поток игры, поэтому действуют три правила:

  • У ответа есть срок. Сторожевой поток проверяет запросы каждые 125 мс и считает ASK просроченным, когда тому больше 250 мс. Тогда хост сам отвечает END ok=1 за мод и пишет номер запроса в журнал. Обычно это происходит через 250–375 мс после запроса, плюс задержки планировщика и цикла отправки. Медленный мод теряет право вето на этот запрос. Если JVM завершилась, хост перестаёт ждать ответов и выключается.
  • Не читайте ответы в потоке, который отвечает. Мод, который из обработчика события вызывает игру, иначе её заблокирует. Поэтому обе стороны читают и рассылают кадры в разных потоках.
  • У кадров свой канал. Мод, который печатает через System.out, ничего не ломает: его вывод попадает в консоль хоста. Строку, которую хост не разобрал, он сообщает как нечитаемый вывод Coderpack. Для журнала используйте API загрузчика: он подписывает каждую строку идентификатором мода.

Когда хост завершается

На Windows хост пытается поместить JVM в job object с JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, чтобы JVM завершалась вместе с ним. Игра в этот объект не входит. Гарантия работает, только если объект создан, ограничение настроено и JVM к нему присоединена. О первых двух сбоях хост предупреждает. Сбой присоединения пока проходит молча.

Когда игра падает

Эти флаги помогают найти сломанный хук без пересборки:

Флаг Что делает
--skip gold,position Не загружает эти модули агента
--only health Загружает только этот модуль. core, bus и names грузятся всегда
--no-hook goldEpilogue Оставляет модуль, но пропускает эту точку
--trace Пишет в журнал каждое срабатывание хука
--no-ask Оставляет хуки, но никогда не ждёт ответа

Имя модуля — это имя файла агента без числового префикса. Между попытками перезапускайте игру. Внедрённый агент может остаться в игре после выхода хоста, и новый хост попытается повторно изменить уже перехваченную инструкцию.

Список переключаемых точек лаунчер получает через --hooks. Он спрашивает protocol.exe из папки игры: только эта копия знает, какой агент в неё встроен.

Сборка

Нужны Rust 1.98 с MSVC toolchain и LLVM: frida-sys запускает bindgen, а тому нужна libclang. .cargo/config.toml направляет LIBCLANG_PATH в C:\Program Files\LLVM\bin, если переменная ещё не задана.

cargo build --release
cargo test --release

Результат — target/release/protocol.exe. Предупреждение линковщика LNK4098 возникает из-за статического CRT в frida-core и динамического в Rust. Так и должно быть.

Агента сборка берёт из первого найденного источника:

  1. $PROTOCOL_AGENT
  2. Соседний ../coderpack/agent/src
  3. agent.zip релиза coderpack, закреплённого в dependencies.json. Архив кэшируется в build/agent/

Третий источник позволяет собрать одиночный клон. coderpack генерирует таблицу адресов, а не хранит её в репозитории, так что исходников не всегда хватает, а ресурса релиза хватает всегда. Источник агента хост печатает при запуске.

Тесты проверяют кодек кадров, минификатор, сборщик агента и границу с JVM. Два сквозных теста запускают настоящую JVM Coderpack без модов и проверяют, что каждый ASK получает кадр, понятный хосту. Они берут самые свежие JAR-файлы api и zygote из соседнего checkout coderpack или из ~/.m2/repository/dev/ancaria/coderpack. Если файлов нет, тесты сообщают о пропуске и проходят, поэтому для чисто Rust-сборки JDK не нужен.

В vendor/frida лежит исправленная копия крейта frida 0.17.2. Исходная версия для всех сообщений, кроме frida:rpc, приводит user_data обратного вызова к неверному типу, и первый send() агента ронял хост с 0xC0000005. Исправление описано в vendor/README.md. Эта проверка требует python в PATH, но обходится без игры:

cargo run --example message_check

Релизы

CI работает на Windows при каждом push в master, для каждого pull request и по запросу. Он собирает и тестирует release-версию и прикладывает protocol.exe к запуску как артефакт. cargo fmt --check тоже выполняется, но сборку не роняет.

На master CI читает version из Cargo.toml. Если тега v<версия> ещё нет, CI создаёт его и публикует релиз с protocol.exe. Чтобы выпустить релиз, поднимите версию командой pwsh tools/version.ps1 <версия>. Лаунчер скачивает этот файл, когда рядом с ним нет checkout протокола.

Изменение агента доходит до игроков только через релиз этого репозитория. Порядок релизов всего загрузчика описан в CONTRIBUTING.

Лицензия

MIT, см. LICENSE.

Releases

Packages

Contributors

Languages