libs\ — общая библиотека для всех модов
Загрузчик игры даёт каждому моду свой class loader, поэтому моды не видят друг друга напрямую. Но у всех этих загрузчиков родителем является системный, а значит всё, что добавлено в его classpath, видно каждому моду. Агент теперь на старте дописывает все jar из <home>\libs\ в системный classpath. Одна копия библиотеки на всех, модам не нужно ничего вшивать в себя. Проверено стендом: библиотека testlib лежит в libs\, мод её использует, и в jar мода её нет.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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),
|
||||
|
||||
+41
-3
@@ -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 ''
|
||||
|
||||
Reference in New Issue
Block a user