This repository has been archived on 2026-10-17. You can view files and clone it. You cannot open issues or pull requests or push a commit.
SashegDev 88b90802a7 Агент ищет свой дом, а не доверяет месту jar
Путь к агенту в конфиге лаунчера переписывает сам лаунчер, и однажды он
потерял целый сегмент пути: осталось ...\rsx-mod\rsx-bridge.jar вместо
...\rsx-mod\bridge\rsx-bridge.jar. JVM не смог загрузить агент, и игра падала с
error2 при входе в моды.

Две правки:
- агент кладём в корень модлоадера, а не в bridge\: тогда его каталог и есть
  дом, и mods\, libs\ и config\ находятся без угадывания;
- в env пишем слеши вперёд. Лаунчер переписывает конфиг своим properties-писателем
  и портит именно обратные слеши, а у прямого слеша экранировать нечего.

Заодно premain теперь поднимается вверх, пока не найдёт каталог с mods\ или
libs\, вместо того чтобы считать родителем jar единственно верным ответом.

И ещё: лаунчер вместе с env потерял gamePath1, установщик теперь проверяет и
восстанавливает его.

Заодно выяснилось, что апдейтер всё же чинит app.jar ("Final repair: app.jar") -
наш фильтр не сработал, потому что маркер с базовым хешем к тому моменту не
совпадал. Сейчас это лечится автоматически, но в логах видно.
2026-10-17 09:30:09 +03:00

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 и updater.exe лаунчера и прописывает -javaagent в его конфиг. Пути можно переопределить:

install.cmd --home D:\rsx-mod --game D:\RustexRemake

Лаунчер должен быть закрыт. Если он запущен с правами администратора, app.jar не перезаписать — установщик об этом скажет.

Обычный ярлык работает: патч лаунчера и патч апдейтера рассчитаны на то, что обновление придёт и не снесёт модлоадер. start-launcher.cmd остаётся как запасной путь в обход апдейтера.

Как это работает

  • Агент (rsx-bridge.jar) подключается через -javaagent и в premain читает профиль сборки игры (profiles/latest.rsxp): хеш jar, обфусцированные имена нужных классов и полей. Ничего не хардкодится под конкретную сборку.
  • Патчи клиента — A2 ставит joml.fastmath до загрузки JOML, A6 подменяет таймер телеметрии на «глухой». Оба сделаны на живом клиенте: первые версии A6 роняли игру с NullPointerException и Timer already cancelled.
  • Моды грузятся родным загрузчиком игры (ilxq → special.Main) — так же, как моды с сервера, и выполняются в игровом потоке.
  • Несколько модов работают вместе — см. «Почему один drain» ниже.
  • Переносимость модов. Мод собирается один раз против стабильного 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.

Моды

Мод — это jar с манифестом

Rsx-Mod-Entrypoint: com.example.MyMod

и классом, реализующим RsxMod. Собирается один раз против rsx-api.jar и дальше не пересобирается: все обфусцированные имена мод не знает, бриф разрешает их через профиль сборки.

Общая библиотека кладётся в %LOCALAPPDATA%\rsx-mod\mods\..\libs\ — то есть %LOCALAPPDATA%\rsx-mod\libs\: агент подмешивает эти jar в системный class path при старте, и моды видят библиотеку, не вшивая её в себя.

Репозитории:

что где
модлоадер sasheg/rsx-modloader
общая библиотека sasheg/rsxm-lib
эталонный мод sasheg/rsxm-example
HUD sasheg/rsxm-hud

Готовый шаблон простого мода — в mod-template\, пример на библиотеке — в rsxm-example.

Rsx.everyTicks(20, this::onSecond);      // раз в 20 игровых тиков
Rsx.every(5000L, this::poll);            // раз в 5 секунд реального времени
Rsx.onGameThread(this::doIt);            // на следующем тике
Rsx.on("playerJoin", this::hello);       // по событию

События: tick, worldChange, playerJoin, playerLeave, screenOpen, screenClose. Игрок и мир — типизированные обёртки, а не Object:

Rsx.Player p = Rsx.player();
p.getName(); p.getX(); p.getY(); p.getZ(); p.getHealth(); p.isAlive();
Rsx.World w = Rsx.world();
w.getTime(); w.isClient();

everyTicks стоит предпочитать every: миллисекунды — это часы, а тик — это тик, и при лагах миллисекундный таймер прыгает, а тиковый нет.

Если задача мода падает, мост зовёт RsxMod.onError, а после пяти подряд падений вообще отбрасывает задачу — чтобы сломанный мод не залил лог.

Почему один drain

Родной загрузчик игры (ilxq) на каждый jar создаёт новый загрузчик классов, поэтому моды не видят друг друга и не конфликтуют за special.Main. Но тик-хук он запоминает в статическом поле, которое перезаписывается при каждой загрузке:

do = new ilxq();   // свой загрузчик на мод
...
int = method2;     // статическое поле: помнит только ПОСЛЕДНИЙ мод

Из-за этого getRunnableList() вызывается у одного мода — последнего загруженного. Мод, чей хук не запомнили, молча ничего не делает: его onLoad отработал, его задачи висят в очереди, и их никто не разбирает.

Поэтому в мосте одна очередь на всех и один drain: какой бы Main игра ни позвала, он выполняет работу всех модов. Собственность задачи запоминается в ThreadLocal в момент onLoad, поэтому в логе ошибки видно, чей это код.

Проверяется тестом: bridge\src\test\python\run_test.py грузит три мода и требует, чтобы работу всех трёх видно было в логе.

Тест-сервер

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.

S
Description
RustEx Reamake Modloader that allows to modify the client game BANNABLE!!! DO NOT USE (i have banned)
Readme MIT 535 KiB
v0.2.0 Latest
2026-10-16 23:25:47 +00:00
Languages
Java 80.5%
Python 10.5%
PowerShell 8.7%
Batchfile 0.3%