From 13ddc0d0f5e607b46b176ee3a8878f52317fef2f Mon Sep 17 00:00:00 2001 From: SashegDev Date: Sat, 17 Oct 2026 02:38:34 +0300 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFlibs\=20=E2=80=94=20=D0=BE=D0=B1=D1=89?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=81=D0=B5=D1=85=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Загрузчик игры даёт каждому моду свой class loader, поэтому моды не видят друг друга напрямую. Но у всех этих загрузчиков родителем является системный, а значит всё, что добавлено в его classpath, видно каждому моду. Агент теперь на старте дописывает все jar из \libs\ в системный classpath. Одна копия библиотеки на всех, модам не нужно ничего вшивать в себя. Проверено стендом: библиотека testlib лежит в libs\, мод её использует, и в jar мода её нет. --- bridge/src/main/java/rsx/RsxAgent.java | 36 +++++++++++++++ .../test/fakeclient/lib/rsxtest/testlib.java | 22 ++++++++++ bridge/src/test/mod/ThirdMod.java | 4 ++ bridge/src/test/python/run_test.py | 22 +++++++++- scripts/build.ps1 | 44 +++++++++++++++++-- 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 bridge/src/test/fakeclient/lib/rsxtest/testlib.java diff --git a/bridge/src/main/java/rsx/RsxAgent.java b/bridge/src/main/java/rsx/RsxAgent.java index 9433c1e..20a64e6 100644 --- a/bridge/src/main/java/rsx/RsxAgent.java +++ b/bridge/src/main/java/rsx/RsxAgent.java @@ -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) { diff --git a/bridge/src/test/fakeclient/lib/rsxtest/testlib.java b/bridge/src/test/fakeclient/lib/rsxtest/testlib.java new file mode 100644 index 0000000..07c8a53 --- /dev/null +++ b/bridge/src/test/fakeclient/lib/rsxtest/testlib.java @@ -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; + } +} diff --git a/bridge/src/test/mod/ThirdMod.java b/bridge/src/test/mod/ThirdMod.java index d5ae309..36b914a 100644 --- a/bridge/src/test/mod/ThirdMod.java +++ b/bridge/src/test/mod/ThirdMod.java @@ -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() { diff --git a/bridge/src/test/python/run_test.py b/bridge/src/test/python/run_test.py index 8752f77..137264a 100644 --- a/bridge/src/test/python/run_test.py +++ b/bridge/src/test/python/run_test.py @@ -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), diff --git a/scripts/build.ps1 b/scripts/build.ps1 index dc5a75a..004c8e0 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -127,13 +127,51 @@ Copy-Item (Join-Path $Repo 'mod-template\src\main\java\com\example\ExampleMod.ja Copy-Item (Join-Path $Dist 'rsx-api.jar') "$tpl\libs\" -Force @' @echo off -rem Builds this mod and drops the jar into the client's mods folder. +rem Builds the mod and drops the jar into the client's mods folder. +rem Only a JDK is needed: no Gradle, no network, nothing but javac and jar. +setlocal cd /d "%~dp0" -call gradle jar + +if not exist "build\classes" mkdir "build\classes" +if not exist "build\mods" mkdir "build\mods" + +set ENTRY=com.example.ExampleMod + +rem where the modloader looks for mods; override with set MODS_DIR=... +if "%MODS_DIR%"=="" set MODS_DIR=%LOCALAPPDATA%\rsx-mod\mods + +echo [1/3] compiling... +dir /s /b src\main\java\*.java > build\sources.txt +javac -encoding UTF-8 --release 8 -nowarn -cp libs\rsx-api.jar -d build\classes @build\sources.txt +if errorlevel 1 ( + echo. + echo Сборка не удалась. + exit /b 1 +) + +echo [2/3] packaging example-mod.jar... +if exist "build\mods\example-mod.jar" del "build\mods\example-mod.jar" +jar cfm "build\mods\example-mod.jar" manifest.txt -C build/classes . if errorlevel 1 exit /b 1 + +echo [3/3] installing... +if not exist "%MODS_DIR%" ( + echo. + echo Папка модов не найдена: %MODS_DIR% + echo Скопируй build\mods\example-mod.jar в неё сам. + exit /b 0 +) +copy /y "build\mods\example-mod.jar" "%MODS_DIR%\" >nul echo. -echo Скопируй build\libs\example-mod.jar в working_dir\mods +echo Готово: build\mods\example-mod.jar скопирован в %MODS_DIR% '@ | Set-Content -LiteralPath (Join-Path $tpl 'build.cmd') -Encoding ASCII + +# the manifest carries the entry point the bridge looks for +$entry = (Select-String -LiteralPath "$tpl\src\main\java\com\example\ExampleMod.java" ` + -Pattern '^package\s+([\w.]+)' | ForEach-Object { $_.Matches[0].Groups[1].Value }) +"Manifest-Version: 1.0`r`nRsx-Mod-Entrypoint: $entry.ExampleMod`r`n`r`n" | + Set-Content -LiteralPath (Join-Path $tpl 'manifest.txt') -Encoding ASCII + Say 'mod-template готов' Write-Host ''