diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java
deleted file mode 100644
index 70d2c0c9..00000000
--- a/AccessWidener/src/main/java/de/steamwar/AccessWidenerScanner.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * This file is a part of the SteamWar software.
- *
- * Copyright (C) 2026 SteamWar.de-Serverteam
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package de.steamwar;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.instrument.Instrumentation;
-import java.lang.reflect.Field;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.nio.file.Path;
-import java.util.*;
-import java.util.logging.Logger;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-/**
- * Scans all plugin ClassLoaders for "plugin.accesswidener" resources
- * and returns the parsed entries.
- */
-public final class AccessWidenerScanner {
-
- private static final Logger LOG = Logger.getLogger("AccessWidenerScanner");
-
- private AccessWidenerScanner() {
- }
-
- /**
- * Scans every ClassLoader visible from the already-loaded classes for
- */
- public static List scanAll(Instrumentation inst) {
- List allEntries = new ArrayList<>();
- Set seen = Collections.newSetFromMap(new IdentityHashMap<>());
-
- for (Class> clazz : inst.getAllLoadedClasses()) {
- ClassLoader cl = clazz.getClassLoader();
- if (cl == null) continue; // bootstrap — skip
- if (!seen.add(cl)) continue; // already processed
- if (!isPluginClassLoader(cl)) continue;
-
- allEntries.addAll(scanClassLoader(cl));
- }
-
- return allEntries;
- }
-
- /**
- * Uses getResources() so it finds ALL matching files on the loader's classpath.
- */
- public static List scanClassLoader(ClassLoader cl) {
- List entries = new ArrayList<>();
-
- try {
- for (URL url : findAccessWideners(cl)) {
- try (InputStream in = url.openStream()) {
- List parsed = AccessWidenerParser.parse(in);
- LOG.info("[AccessWidener] Loaded " + parsed.size() + " entries from " + url);
- entries.addAll(parsed);
- } catch (IOException e) {
- LOG.warning("[AccessWidener] Failed to read " + url + ": " + e.getMessage());
- }
- }
- } catch (IOException e) {
- LOG.warning("[AccessWidener] Failed to scan " + cl + ": " + e.getMessage());
- }
-
- return entries;
- }
-
- public static List findAccessWideners(ClassLoader cl) throws IOException {
- List results = new ArrayList<>();
-
- // Standard path — works on most Paper versions
- if (cl instanceof URLClassLoader urlCl) {
- return scanUrls(urlCl.getURLs());
- }
-
- // Newer Paper — PluginClassLoader stores the jar as a field
- try {
- // Try common field names used across Paper versions
- for (String fieldName : List.of("file", "jarFile", "pluginJar", "source")) {
- try {
- Field f = cl.getClass().getDeclaredField(fieldName);
- f.setAccessible(true);
- Object value = f.get(cl);
- if (value instanceof File file) {
- return scanUrls(new URL[]{ file.toURI().toURL() });
- }
- if (value instanceof Path path) {
- return scanUrls(new URL[]{ path.toUri().toURL() });
- }
- } catch (NoSuchFieldException ignored) {}
- }
- } catch (Exception e) {
- LOG.warning("[AccessWidener] Could not extract JAR path from " + cl + ": " + e.getMessage());
- }
-
- return results;
- }
-
- private static List scanUrls(URL[] urls) throws IOException {
- List results = new ArrayList<>();
- for (URL url : urls) {
- if (!url.getPath().endsWith(".jar")) continue;
- try (ZipInputStream zip = new ZipInputStream(url.openStream())) {
- ZipEntry entry;
- while ((entry = zip.getNextEntry()) != null) {
- if (entry.getName().endsWith(".accesswidener")) {
- results.add(new URL("jar:" + url + "!/" + entry.getName()));
- }
- }
- }
- }
- return results;
- }
-
- /**
- * Returns true if the classloader looks like a Paper plugin classloader.
- * Checks by name so we don't need a direct dependency on Paper internals.
- */
- public static boolean isPluginClassLoader(ClassLoader cl) {
- String name = cl.getClass().getName();
- return name.contains("PluginClassLoader") // legacy Paper / Spigot
- || name.contains("PaperPluginLoader") // Paper 1.20.5+
- || name.contains("PaperSimplePluginManager"); // just in case
- }
-}
diff --git a/AccessWidener/src/main/java/de/steamwar/Agent.java b/AccessWidener/src/main/java/de/steamwar/Agent.java
index a786fed8..6558508c 100644
--- a/AccessWidener/src/main/java/de/steamwar/Agent.java
+++ b/AccessWidener/src/main/java/de/steamwar/Agent.java
@@ -19,7 +19,11 @@
package de.steamwar;
+import java.io.File;
+import java.io.IOException;
import java.lang.instrument.Instrumentation;
+import java.util.ArrayList;
+import java.util.List;
import java.util.logging.Logger;
/**
@@ -38,14 +42,11 @@ import java.util.logging.Logger;
*/
public class Agent {
private Agent() {
- /* This utility class should not be instantiated */
+ throw new IllegalStateException("Utility class");
}
private static final Logger LOG = Logger.getLogger("AccessWidenerAgent");
- // Exposed so tests or other code can inspect the live transformer
- static volatile WideningTransformer transformer;
-
// -javaagent: startup
public static void premain(String args, Instrumentation inst) {
init(inst);
@@ -54,13 +55,22 @@ public class Agent {
private static void init(Instrumentation inst) {
LOG.info("[AccessWidener] Agent initialising.");
- WideningTransformer t = new WideningTransformer(inst);
- transformer = t;
-
- // --- Phase 2: register transformer for future class loads ---
- // canRetransform=true so we can call retransformClasses() later
- inst.addTransformer(t, true);
+ List entries = new ArrayList<>();
+ File file = new File(new File(".").getAbsoluteFile(), "plugins/");
+ File[] files = file.listFiles();
+ if (files == null) files = new File[0];
+ for (File jarFile : files) {
+ if (!jarFile.isFile()) continue;
+ if (!jarFile.getName().endsWith(".jar")) continue;
+ try {
+ entries.addAll(Utils.findAndParseAccessWideners(jarFile.toPath()));
+ } catch (IOException e) {
+ LOG.warning("Failed to parse access wideners from " + jarFile.getAbsolutePath());
+ }
+ }
+ LOG.info("[AccessWidener] Loaded " + entries.size() + " access wideners.");
+ inst.addTransformer(new WideningTransformer(entries), false);
LOG.info("[AccessWidener] Agent ready.");
}
}
diff --git a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java
index 3e640694..a5549762 100644
--- a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java
+++ b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java
@@ -19,10 +19,12 @@
package de.steamwar;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
import java.util.List;
import java.util.Set;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
@@ -33,6 +35,8 @@ import java.util.stream.Collectors;
*/
public class ClassPatcher {
+ private static final Logger LOG = Logger.getLogger("ClassPatcher");
+
private final List entries;
/** Pre-computed set of targeted internal names for fast filtering. */
@@ -46,18 +50,22 @@ public class ClassPatcher {
}
/**
- * Patches {@code classBytes} if {@code internalName} is targeted.
+ * Patches {@code classBytes} if {@code className} is targeted.
*
* @return patched bytes, or {@code null} if no changes were needed
*/
- public byte[] patch(String internalName, byte[] classBytes) {
- if (!targets.contains(internalName)) return null;
+ public byte[] patch(String className, byte[] classBytes) {
+ if (!targets.contains(className)) return null;
- ClassReader cr = new ClassReader(classBytes);
- // COMPUTE_FRAMES would require the full classpath; we only touch flags so 0 is fine
- ClassWriter cw = new ClassWriter(cr, 0);
- cr.accept(new ClassTransformer(cw, internalName, entries), 0);
- return cw.toByteArray();
+ try {
+ ClassReader cr = new ClassReader(classBytes);
+ ClassWriter cw = new ClassWriter(cr, 0);
+ cr.accept(new ClassTransformer(cw, className, entries), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return cw.toByteArray();
+ } catch (Exception e) {
+ LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage());
+ return null;
+ }
}
}
diff --git a/AccessWidener/src/main/java/de/steamwar/Utils.java b/AccessWidener/src/main/java/de/steamwar/Utils.java
new file mode 100644
index 00000000..1a5bc541
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/Utils.java
@@ -0,0 +1,60 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class Utils {
+
+ private static final Logger LOG = Logger.getLogger("AccessWidenerAgent");
+
+ private Utils() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ public static List findAndParseAccessWideners(Path jarPath) throws IOException {
+ List results = new ArrayList<>();
+
+ try (ZipFile zip = new ZipFile(jarPath.toFile())) {
+ Enumeration extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (entry.isDirectory() || !entry.getName().endsWith(".accesswidener")) continue;
+
+ try (InputStream in = zip.getInputStream(entry)) {
+ results.addAll(AccessWidenerParser.parse(in));
+ } catch (IOException e) {
+ LOG.warning("[AccessWidener] Failed to parse " + entry.getName()
+ + " in " + jarPath.getFileName() + ": " + e.getMessage());
+ }
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java
index dd14a9a0..2215c769 100644
--- a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java
+++ b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java
@@ -19,17 +19,9 @@
package de.steamwar;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassWriter;
-
import java.lang.instrument.ClassFileTransformer;
-import java.lang.instrument.Instrumentation;
-import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
+import java.util.List;
/**
* Transforms class bytecode to apply access widening rules.
@@ -39,111 +31,19 @@ import java.util.stream.Collectors;
*/
public class WideningTransformer implements ClassFileTransformer {
- private static final Logger LOG = Logger.getLogger("WidenerTransformer");
+ private final ClassPatcher patcher;
- private final Instrumentation instrumentation;
-
- /**
- * All entries collected across all plugins. Thread-safe for concurrent plugin loads.
- */
- private final List entries = new CopyOnWriteArrayList<>();
-
- /**
- * ClassLoaders we have already scanned, to avoid re-scanning.
- */
- private final Set scannedLoaders = Collections.synchronizedSet(Collections.newSetFromMap(new IdentityHashMap<>()));
-
- public WideningTransformer(Instrumentation instrumentation) {
- this.instrumentation = instrumentation;
+ public WideningTransformer(List entries) {
+ patcher = new ClassPatcher(entries);
}
- /**
- * Add entries — used during initial scan before the transformer is registered.
- */
- public void addEntries(List newEntries) {
- entries.addAll(newEntries);
- }
-
- // -------------------------------------------------------------------------
- // ClassFileTransformer
- // -------------------------------------------------------------------------
-
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
-
- // Check for new plugin classloaders we haven't seen yet
- if (loader != null && AccessWidenerScanner.isPluginClassLoader(loader) && scannedLoaders.add(loader)) {
- onNewPluginClassLoader(loader);
- }
-
- // No entries for this class — skip transformation entirely
- if (className == null) return classfileBuffer;
- boolean relevant = entries.stream().anyMatch(e -> e.targets(className));
- if (!relevant) return classfileBuffer;
-
- // Apply widening via ASM
- try {
- ClassReader cr = new ClassReader(classfileBuffer);
- ClassWriter cw = new ClassWriter(cr, 0);
- cr.accept(new ClassTransformer(cw, className, entries), 0);
- return cw.toByteArray();
- } catch (Exception e) {
- LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage());
+ byte[] result = patcher.patch(className, classfileBuffer);
+ if (result == null) {
return classfileBuffer;
- }
- }
-
- // -------------------------------------------------------------------------
- // New ClassLoader detection
- // -------------------------------------------------------------------------
-
- private void onNewPluginClassLoader(ClassLoader loader) {
- List newEntries = AccessWidenerScanner.scanClassLoader(loader);
- if (newEntries.isEmpty()) return;
-
- entries.addAll(newEntries);
- LOG.info("[AccessWidener] Picked up " + newEntries.size() + " new entr" + (newEntries.size() == 1 ? "y" : "ies") + " from " + loader);
-
- // Retransform already-loaded classes that are now targeted
- scheduleRetransform(newEntries);
- }
-
- /**
- * Retransformation cannot be called from within a transform() call,
- * so we dispatch it to a short-lived daemon thread.
- */
- private void scheduleRetransform(List newEntries) {
- Set targets = newEntries.stream().map(e -> e.target().replace('/', '.')).collect(Collectors.toSet());
-
- Thread t = new Thread(() -> retransform(targets), "access-widener-retransform");
- t.setDaemon(true);
- t.start();
- }
-
- private void retransform(Set dotNames) {
- List toRetransform = Arrays.stream(instrumentation.getAllLoadedClasses())
- .filter(c -> dotNames.contains(c.getName()))
- .filter(instrumentation::isModifiableClass)
- .collect(Collectors.toList());
-
- if (toRetransform.isEmpty()) return;
-
- LOG.info("[AccessWidener] Retransforming " + toRetransform.size() + " class(es).");
- List failed = new ArrayList<>();
-
- for (Class> clazz : toRetransform) {
- try {
- instrumentation.retransformClasses(clazz);
- } catch (UnmodifiableClassException | UnsupportedOperationException e) {
- failed.add(clazz.getName());
- }
- }
-
- if (!failed.isEmpty()) {
- LOG.warning("[AccessWidener] The following classes were already loaded before the agent"
- + " and cannot be retransformed. Add -javaagent: to your start script"
- + " to fix this:");
- failed.forEach(name -> LOG.warning(" - " + name));
+ } else {
+ return result;
}
}
}
diff --git a/BauSystem/build.gradle.kts b/BauSystem/build.gradle.kts
index b6180f3f..aa03f184 100644
--- a/BauSystem/build.gradle.kts
+++ b/BauSystem/build.gradle.kts
@@ -20,22 +20,15 @@
plugins {
`java-library`
alias(libs.plugins.shadow)
- id("io.papermc.paperweight.userdev") version "1.7.1"
}
tasks.build {
finalizedBy(tasks.shadowJar)
}
-repositories {
- maven("https://repo.papermc.io/repository/maven-public/")
-}
-
dependencies {
implementation(project(":BauSystem:BauSystem_RegionFixed"))
implementation(project(":BauSystem:BauSystem_Main"))
-
- paperweight.paperDevBundle("1.21.1-R0.1-SNAPSHOT")
}
tasks.register("DevBau21") {