# rsx-modloader Модлоадер для RustexRemake: Java-моды в папке `mods`, подключаются через `-javaagent`, `game.jar` не патчится. Один раз поставил — моды переживают обновление игры. В лаунчере появляется выбор режима прямо на кнопке «Играть»: **с модами** или **ванилла**. ``` mod-template\ пример мода, собирается один раз bridge\ агент (-javaagent) и его исходники launcher-patch\ патч лаунчера: окно выбора и защита от автоапдейта testserver\ локальный сервер, принимает любой ник и токен scripts\ сборка, установка, запуск, удаление third_party\asm\ ASM (BSD-3), зашит внутрь rsx-launcher-patch.jar ``` ## Установка build.cmd собрать dist\ (нужен только JDK) install.cmd поставить модлоадер start-launcher.cmd запускать лаунчер uninstall.cmd вернуть всё как было `install.cmd` копирует рантайм в `%LOCALAPPDATA%\rsx-mod`, патчит `app.jar` лаунчера и прописывает `-javaagent` в его конфиг. Пути можно переопределить: install.cmd --home D:\rsx-mod --game D:\RustexRemake **Лаунчер должен быть закрыт.** Если он запущен с правами администратора, `app.jar` не перезаписать — установщик об этом скажет. Запускай лаунчер через `start-launcher.cmd`, а не ярлыком: `updater.exe` сверяет хеш `app.jar` с манифестом и при патченом jar заново скачивает официальный, стирая патч. ## Как это работает * **Агент** (`rsx-bridge.jar`) подключается через `-javaagent` и в `premain` читает профиль сборки игры (`profiles/latest.rsxp`): хеш jar, обфусцированные имена нужных классов и полей. Ничего не хардкодится под конкретную сборку. * **Патчи клиента** — A2 ставит `joml.fastmath` до загрузки JOML, A6 подменяет таймер телеметрии на «глухой». Оба сделаны на живом клиенте: первые версии A6 роняли игру с `NullPointerException` и `Timer already cancelled`. * **Моды** грузятся родным загрузчиком игры (`ilxq` → `special.Main`) — так же, как моды с сервера, и выполняются в игровом потоке. * **Переносимость модов.** Мод собирается один раз против стабильного API. Мост сам переводит логические имена в имена текущей сборки, поэтому ре-обфускация вендора не ломает уже собранные моды. ## Патч лаунчера Лаунчер — сторонний продукт, и у него три особенности, которые мешают: * **Гонка за состоянием кнопки.** `xa.run()` вызывает `ha.y()` (ставит `PLAY`), а `fa.loadedWeb()` позже ставит `ERROR`. Состояния — это индексы кнопок в `web/launcher/main.html`, их шесть, поэтому `ERROR=6` скрывает все кнопки. При медленной загрузке страницы `loadedWeb` затирает `PLAY` — кнопки просто нет. Патч восстанавливает состояние после `loadedWeb`. * **Автоапдейтер.** Патченый `app.jar` отличается от манифеста, и лаунчер считает себя устаревшим в двух местах: `sb.q()` (размер и хеш файла) и `ha` (хеш рабочего jar, отсюда «обновление найдено, перезапустите лаунчер»). Оба закрыты через `rsx.Patch`, который действует только на файл с именем `app.jar` и только при наличии маркера `.rsx-patched`. * **Ремонт своего апдейтера.** При старте лаунчер проверяет хеш `updater.exe` и, не найдя вендорскую сборку, скачивает чистую копию поверх (`jb` → `l.ALLATORIxDEMO()` → «updater.exe»). Метод `jb` заменён на `rsx.Patch.skipUpdaterRepair()`. Возврат к обычному поведению: удалить `.rsx-patched` или запустить лаунчер с `-Drsx.allowLauncherUpdate=true`. ## Патч апдейтера `updater.exe` — это PE-заглушка Launch4j, к которой обычным zip просто пришит jar (payload начинается с offset 65024 и идёт до EOF). Заглушка запускает jar из себя, поэтому патчить надо именно хвост, а PE-заголовок не трогаем. В payload вставляются два хука: * `UpdaterGui.runLauncher(Z)V` → `rsx.UpdaterHook.beforeLauncher(...)` — перед запуском лаунчера `app.jar` патчится заново, если он оказался вендорским (апдейтер достаёт его из своего локального кэша даже когда скачивать нечего). * `DownloadEngine.findFilesToDownload` → `rsx.UpdaterHook.filterUpdates(...)` — `app.jar` убирается из списка на скачивание, **только** если хеш в манифесте совпадает с `baseSha256` из `.rsx-patched`. Хеш другой — значит настоящее обновление, и файл скачивается как обычно. `baseSha256` — это хеш нетронутого `app.jar`; патчер пишет его в `.rsx-patched` сам, до того как перезаписать файл. При настоящем обновлении маркер перезаписывается новым значением. Хук идемпотентен: повторный `patchupdater` берёт pristine-копию из `updater.exe.orig`, а если `updater.exe` заменил вендор — обновляет бэкап. Перед записью собранный jar проверяется загрузкой классов, так что битый патч никогда не попадёт на диск. Лог: `%APPDATA%\kjstudio\rsx-launcher\updater-patch.log`. ## Конфиг лаунчера — осторожно `launcher_config.properties` — это properties-файл: слэш экранирует следующий символ, а `\r` читается как перевод строки. Значение `gamePath1=G:\rustexremake` без экранирования превращается в `G:` + перевод строки + `ustexremake`, и лаунчер перестаёт находить игру. Все правки идут через `java.util.Properties` — в патчере есть команды `getprop` и `setprop`. ## Тест-сервер testserver\start.cmd Локальный сервер: принимает любой ник и токен, отвечает на `/server-ping`, слушает порты 25566–25573 и закидывает в пустой мир. Проверка без игры: python testserver\selftest.py Адрес сервера зашит в коде клиента, поэтому для полного захода в мир нужен редирект (PowerShell **от администратора**): scripts\install-hosts.ps1 прописать 127.0.0.1 для адреса клиента scripts\uninstall-hosts.ps1 откатить ## Сборка build.cmd Нужен только JDK. ASM лежит в `third_party\asm` и зашивается внутрь `rsx-launcher-patch.jar`, поэтому устанавливать модлоадер можно без Gradle и без сети. Готовые файлы кладутся в `dist\`: | файл | что это | |---|---| | `rsx-bridge.jar` | агент, Java 8 | | `rsx-api.jar` | API, против которого собирается мод | | `rsx-launcher-patch.jar` | патчер лаунчера, всё внутри | | `mod-template\` | заготовка мода | ## Тесты python bridge\src\test\python\run_test.py Поднимает фейковый клиент, проверяет профиль, A2, A6, загрузку мода и то, что мод выполняется в игровом потоке, а телеметрия не срабатывает. --- RustexRemake и KJST Launcher — сторонние продукты, они не входят в этот проект. Инструмент работает только на машине пользователя. ASM распространяется по лицензии BSD-3, см. `LICENSE`.