libs\ — общая библиотека для всех модов

Загрузчик игры даёт каждому моду свой class loader, поэтому моды не видят друг
друга напрямую. Но у всех этих загрузчиков родителем является системный, а
значит всё, что добавлено в его classpath, видно каждому моду.

Агент теперь на старте дописывает все jar из <home>\libs\ в системный
classpath. Одна копия библиотеки на всех, модам не нужно ничего вшивать в себя.

Проверено стендом: библиотека testlib лежит в libs\, мод её использует, и в
jar мода её нет.
This commit is contained in:
SashegDev
2026-10-17 02:38:34 +03:00
parent cc1eaece40
commit 13ddc0d0f5
5 changed files with 124 additions and 4 deletions
+36
View File
@@ -47,6 +47,8 @@ public final class RsxAgent {
Resolver.setInstrumentation(inst);
Log.info("rsx bridge starting (home=" + home + ")");
appendLibraries(inst, new File(home, "libs"));
String mode = readMode();
if ("vanilla".equals(mode)) {
Log.info("mode is vanilla: no mods, no patches, agent does nothing");
@@ -488,6 +490,40 @@ public final class RsxAgent {
}
// ----------------------------------------------------------------- utils
/**
* Put every jar from libs\ on the system class path.
*
* The client gives each mod its own class loader, so mods cannot see each
* other - but every one of those loaders has the system loader as its
* parent. Appending here therefore makes a shared library visible to all
* mods, in one copy, and mods never have to bundle it.
*/
private static void appendLibraries(
java.lang.instrument.Instrumentation inst, File dir) {
if (inst == null || dir == null || !dir.isDirectory()) {
return;
}
File[] jars = dir.listFiles();
if (jars == null || jars.length == 0) {
Log.info("no libraries in " + dir);
return;
}
java.util.Arrays.sort(jars);
for (File jar : jars) {
if (!jar.isFile()
|| !jar.getName().toLowerCase().endsWith(".jar")) {
continue;
}
try {
inst.appendToSystemClassLoaderSearch(new java.util.jar.JarFile(jar));
Log.info("library on the class path: " + jar.getName());
} catch (Throwable t) {
Log.error("cannot load library " + jar.getName(), t);
}
}
}
private static File findGameJar() {
String loader = profile.loaderClass;
if (loader == null) {
@@ -0,0 +1,22 @@
package rsxtest;
// Test stand-in for a shared library that lives in libs\ and is appended to the
// system class path. Every mod sees this class through its loader's parent, so
// one copy serves all of them - which is the whole point of the libs\ folder.
public final class testlib {
private static int calls;
private testlib() {
}
public static String greet(String who) {
calls++;
return "hello " + who;
}
public static int calls() {
return calls;
}
}
+4
View File
@@ -2,6 +2,7 @@ package example;
import net.rustexremake.api.Rsx;
import net.rustexremake.api.RsxMod;
import rsxtest.testlib;
/**
* Third and last mod: the one whose getRunnableList() the vendor actually keeps,
@@ -14,6 +15,9 @@ public class ThirdMod implements RsxMod {
@Override
public void onLoad() {
Rsx.info("ThirdMod: loaded, this is the drain the vendor keeps");
// testlib lives in libs\ and is appended to the system class path, so it
// is visible here without being bundled into this mod's jar
Rsx.info("ThirdMod sees the library: " + testlib.greet("third"));
Rsx.everyTicks(1, new Runnable() {
@Override
public void run() {
+21 -1
View File
@@ -79,6 +79,10 @@ def javac(sources, out, cp=None):
raise SystemExit("javac failed")
def bridge_dir():
return os.path.join(RUN, "bridge")
def zip_dir(src, jar):
if os.path.exists(jar):
os.remove(jar)
@@ -132,9 +136,21 @@ def main():
javac([os.path.join(SRC, f) for f in os.listdir(SRC) if f.endswith(".java")],
classes)
# the shared library lives in libs\ and is appended to the system class path
libdir = os.path.join(bridge_dir(), "libs")
shutil.rmtree(libdir, ignore_errors=True)
os.makedirs(libdir, exist_ok=True)
libclasses = os.path.join(RUN, "libclasses")
shutil.rmtree(libclasses, ignore_errors=True)
javac([os.path.join(SRC, "lib", "rsxtest", "testlib.java")], libclasses)
zip_dir(libclasses, os.path.join(libdir, "testlib.jar"))
print("== compiling the mods ==")
# mods compile against the API and the shared library, but package neither:
# the library is on the system class path at runtime
javac([os.path.join(MOD_SRC, f) for f in os.listdir(MOD_SRC)
if f.endswith(".java")], MOD_CLASSES, cp=BRIDGE_JAR)
if f.endswith(".java")], MOD_CLASSES,
cp=BRIDGE_JAR + os.pathsep + libclasses)
game = os.path.join(RUN, "game.jar")
zip_dir(classes, game)
@@ -218,6 +234,10 @@ def main():
("first mod loaded", "mod loaded: example-mod.jar" in out),
("second mod loaded", "mod loaded: second-mod.jar" in out),
("third mod loaded", "mod loaded: third-mod.jar" in out),
("mod sees the shared library",
"ThirdMod sees the library: hello third" in out),
("library was appended to the class path",
"library on the class path: testlib.jar" in out),
("mod 1 work runs", "ExampleMod tick 1" in out),
("mod 2 work runs (not the kept drain)",
"SecondMod run 1" in out),