Модлоадер RustexRemake: агент, патч лаунчера, установщик и сборка

Моды грузятся через -javaagent, game.jar не патчится, переживают ре-обфускацию.

Что внутри:
- агент rsx-bridge.jar: профиль сборки игры, патчи A2/A6, загрузка модов
  через родной загрузчик игры (ilxq -> special.Main)
- патч лаунчера: окно выбора «С модами / Ванилла» по кнопке «Играть»
  и защита от автоапдейта лаунчера
- install/uninstall: всё ставится в %LOCALAPPDATA%\rsx-mod, пути не хардкодятся
- build: три готовых артефакта в dist, нужен только JDK
- testserver: локальный сервер, принимает любой ник и токен

Проверено на живом клиенте: мод грузится и выполняется в игровом потоке,
в логе видно игрока и мир.
This commit is contained in:
SashegDev
2026-10-17 00:32:10 +03:00
commit b8e5df778e
40 changed files with 4730 additions and 0 deletions
@@ -0,0 +1,123 @@
package net.rustexremake.api;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import rsx.ModBridge;
import rsx.Resolver;
/**
* Stable API for mods.
*
* Nothing in this class mentions an obfuscated name. A mod asks for
* "net.minecraft.client.Minecraft" and the bridge looks up what that class is
* called in the build that is currently installed, so a mod compiled once keeps
* working after the vendor renames everything again.
*/
public final class Rsx {
private Rsx() {
}
// ------------------------------------------------------------- identity
public static String buildId() {
return Resolver.profile() == null ? "unknown"
: Resolver.profile().buildId;
}
public static String describe(String logical) {
return Resolver.profile() == null ? logical
: Resolver.profile().describe(logical);
}
public static boolean ready() {
return Resolver.cls("net.minecraft.client.Minecraft") != null;
}
// ------------------------------------------------------------- accessors
public static Object minecraft() {
return Resolver.get("net.minecraft.client.Minecraft", "instance");
}
public static Object player() {
return Resolver.get("net.minecraft.client.Minecraft", "player", minecraft());
}
public static Object world() {
return Resolver.get("net.minecraft.client.Minecraft", "world", minecraft());
}
public static Object currentScreen() {
return Resolver.get("net.minecraft.client.Minecraft", "currentScreen", minecraft());
}
public static Class<?> classOf(String logical) {
return Resolver.cls(logical);
}
public static Object get(String logicalClass, String member) {
return Resolver.get(logicalClass, member);
}
public static Object get(String logicalClass, String member, Object instance) {
return Resolver.get(logicalClass, member, instance);
}
public static void set(String logicalClass, String member, Object instance,
Object value) {
Resolver.set(logicalClass, member, instance, value);
}
public static Object call(String logicalClass, String member, Object... args) {
return Resolver.call(logicalClass, member, args);
}
public static Object call(String logicalClass, String member, Object instance,
Object... args) {
return Resolver.call(logicalClass, member, instance, args);
}
/**
* Reflective escape hatch for members the profile does not carry yet.
* Mods should prefer the named accessors above.
*/
public static Field field(Class<?> owner, String name) {
try {
Field f = owner.getDeclaredField(name);
f.setAccessible(true);
return f;
} catch (Throwable t) {
return null;
}
}
public static Method method(Class<?> owner, String name, Class<?>... args) {
try {
Method m = owner.getDeclaredMethod(name, args);
m.setAccessible(true);
return m;
} catch (Throwable t) {
return null;
}
}
// -------------------------------------------------------------- threading
/** Run a task on the game thread, on the next tick. */
public static void onGameThread(Runnable task) {
ModBridge.post(task);
}
/** Run a task repeatedly on the game thread. */
public static void every(long millis, final Runnable task) {
ModBridge.schedule(millis, task);
}
// ------------------------------------------------------------------ logs
public static void info(String msg) {
rsx.Log.info(msg);
}
public static void warn(String msg) {
rsx.Log.warn(msg);
}
}