From b0715e0922c07630b9f7f23a9052a556711df97e Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:08:56 +0700 Subject: [PATCH] [FEATURE][EXPERIMENTAL] Implement modular class transformation, fix class transform filter --- .../gravit/utils/launch/LaunchOptions.java | 1 + .../pro/gravit/utils/launch/ModuleLaunch.java | 126 +++++++++++++++++- 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java index f6913067..e778a037 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/LaunchOptions.java @@ -9,6 +9,7 @@ public class LaunchOptions { public static final class ModuleConf { + public boolean enableModularClassTransform = true; public List modules = new ArrayList<>(); public List modulePath = new ArrayList<>(); public Map exports = new HashMap<>(); diff --git a/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java b/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java index 202383eb..b04bc191 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/launch/ModuleLaunch.java @@ -5,21 +5,26 @@ import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.LogHelper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.module.Configuration; -import java.lang.module.ModuleFinder; +import java.lang.module.*; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; +import java.nio.ByteBuffer; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class ModuleLaunch implements Launch { private ModuleClassLoader moduleClassLoader; @@ -59,6 +64,10 @@ public ClassLoaderControl init(List files, String nativePath, LaunchOption if(options.moduleConf != null) { // Create Module Layer moduleFinder = ModuleFinder.of(options.moduleConf.modulePath.stream().map(Paths::get).map(Path::toAbsolutePath).toArray(Path[]::new)); + if(options.moduleConf.enableModularClassTransform) { + AtomicReference clRef = new AtomicReference<>(moduleClassLoader); + moduleFinder = new CustomModuleFinder(moduleFinder, clRef); + } ModuleLayer bootLayer = ModuleLayer.boot(); if(options.moduleConf.modules.contains("ALL-MODULE-PATH")) { var set = moduleFinder.findAll(); @@ -182,6 +191,105 @@ private static String getPackageFromClass(String clazz) { return clazz; } + private class CustomModuleFinder implements ModuleFinder { + private final ModuleFinder delegate; + private AtomicReference cl; + + public CustomModuleFinder(ModuleFinder delegate, AtomicReference cl) { + this.delegate = delegate; + this.cl = cl; + } + + @Override + public Optional find(String name) { + return delegate.find(name).map(this::makeModuleReference); + } + + @Override + public Set findAll() { + return delegate.findAll().stream() + .map(this::makeModuleReference) + .collect(Collectors.toSet()); + } + + private CustomModuleReference makeModuleReference(ModuleReference x) { + return new CustomModuleReference(x.descriptor(), x.location().orElse(null), x, cl); + } + } + + private class CustomModuleReference extends ModuleReference { + private final ModuleReference delegate; + private final AtomicReference cl; + + public CustomModuleReference(ModuleDescriptor descriptor, URI location, ModuleReference delegate, AtomicReference cl) { + super(descriptor, location); + this.delegate = delegate; + this.cl = cl; + } + + @Override + public ModuleReader open() throws IOException { + return new CustomModuleReader(delegate.open(), cl, descriptor()); + } + } + + private class CustomModuleReader implements ModuleReader { + private final ModuleReader delegate; + private final AtomicReference cl; + private final ModuleDescriptor descriptor; + + public CustomModuleReader(ModuleReader delegate, AtomicReference cl, ModuleDescriptor descriptor) { + this.delegate = delegate; + this.cl = cl; + this.descriptor = descriptor; + } + + @Override + public Optional find(String name) throws IOException { + return delegate.find(name); + } + + @Override + public Optional open(String name) throws IOException { + ModuleClassLoader classLoader = cl.get(); + if(classLoader == null || !name.endsWith(".class")) { + return delegate.open(name); + } + var inputOptional = delegate.open(name); + if(inputOptional.isEmpty()) { + return inputOptional; + } + try(ByteArrayOutputStream output = new ByteArrayOutputStream()) { + inputOptional.get().transferTo(output); + var realClassName = name.replace("/", ".").substring(0, name.length()-".class".length()-1); + byte[] bytes = classLoader.transformClass(descriptor.name(), realClassName, output.toByteArray()); + return Optional.of(new ByteArrayInputStream(bytes)); + } + } + + @Override + public Optional read(String name) throws IOException { + // TODO class transformation unimplemented + return delegate.read(name); + } + + @Override + public void release(ByteBuffer bb) { + // TODO class transformation unimplemented + delegate.release(bb); + } + + @Override + public Stream list() throws IOException { + return delegate.list(); + } + + @Override + public void close() throws IOException { + delegate.close(); + } + } + private class ModuleClassLoader extends URLClassLoader { private final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader.getSystemClassLoader(); private final List transformers = new ArrayList<>(); @@ -251,9 +359,7 @@ protected Class findClass(String moduleName, String name) { String rawClassName = name.replace(".", "/").concat(".class"); try(InputStream input = getResourceAsStream(rawClassName)) { byte[] bytes = IOHelper.read(input); - for(ClassLoaderControl.ClassTransformer t : transformers) { - bytes = t.transform(moduleName, name, null, bytes); - } + bytes = transformClass(moduleName, name, bytes); clazz = defineClass(name, bytes, 0, bytes.length); } catch (IOException e) { return null; @@ -286,6 +392,16 @@ protected Class findClass(String moduleName, String name) { } } + private byte[] transformClass(String moduleName, String name, byte[] bytes) { + for(ClassLoaderControl.ClassTransformer t : transformers) { + if(!t.filter(moduleName, name)) { + continue; + } + bytes = t.transform(moduleName, name, null, bytes); + } + return bytes; + } + @Override public String findLibrary(String name) { if(nativePath == null) {