diff --git a/ModuleLoader/Exemplemodule/build.gradle b/ModuleLoader/Exemplemodule/build.gradle new file mode 100644 index 0000000..c454989 --- /dev/null +++ b/ModuleLoader/Exemplemodule/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'java' +} + +group = 'org.example' +version = '1.3' + +repositories { + mavenCentral() + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/groups/public/" + } +} + +dependencies { + compileOnly "io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT" + implementation(files("D:\\Users\\User\\IdeaProjects\\EptaListProject\\ModuleLoader\\build\\libs\\ModuleLoader-1.0-SNAPSHOT.jar")) +} + +jar { + +} diff --git a/ModuleLoader/Exemplemodule/settings.gradle b/ModuleLoader/Exemplemodule/settings.gradle new file mode 100644 index 0000000..fc44232 --- /dev/null +++ b/ModuleLoader/Exemplemodule/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'Exemplemodule' \ No newline at end of file diff --git a/ModuleLoader/Exemplemodule/src/main/java/org/example/ExempleClass.java b/ModuleLoader/Exemplemodule/src/main/java/org/example/ExempleClass.java new file mode 100644 index 0000000..e987a49 --- /dev/null +++ b/ModuleLoader/Exemplemodule/src/main/java/org/example/ExempleClass.java @@ -0,0 +1,7 @@ +package org.example; + +public class ExempleClass { + public static void test() { + System.out.println("Local class get"); + } +} diff --git a/ModuleLoader/Exemplemodule/src/main/java/org/example/ExempleModule.java b/ModuleLoader/Exemplemodule/src/main/java/org/example/ExempleModule.java new file mode 100644 index 0000000..391c697 --- /dev/null +++ b/ModuleLoader/Exemplemodule/src/main/java/org/example/ExempleModule.java @@ -0,0 +1,21 @@ +package org.example; + +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.text.Component; +import opg.p2vman.moduleloader.module.JavaModule; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class ExempleModule extends JavaModule implements Listener { + @Override + public void onEnable() { + getLogger().info("Hi from module"); + ExempleClass.test(); + getServer().getPluginManager().registerEvents(this, getPlugin()); + } + + @EventHandler + public static void chatevent(AsyncChatEvent event) { + event.message(Component.text("AAAAAAAAAAAAAa")); + } +} diff --git a/ModuleLoader/Exemplemodule/src/main/resources/module.json b/ModuleLoader/Exemplemodule/src/main/resources/module.json new file mode 100644 index 0000000..04793d5 --- /dev/null +++ b/ModuleLoader/Exemplemodule/src/main/resources/module.json @@ -0,0 +1,4 @@ +{ + "main_class": "org.example.ExempleModule", + "name": "ExempleModule" +} \ No newline at end of file diff --git a/ModuleLoader/build.gradle b/ModuleLoader/build.gradle new file mode 100644 index 0000000..2f94c56 --- /dev/null +++ b/ModuleLoader/build.gradle @@ -0,0 +1,51 @@ +plugins { + // id 'java' + id 'java-library' +} + +group = 'opg.eptalist' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/groups/public/" + } +} + +dependencies { + compileOnly "io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT" + implementation 'org.ow2.asm:asm:9.6' +} + +def targetJavaVersion = 17 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release.set(targetJavaVersion) + } +} + +processResources { + def props = [version: version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} diff --git a/ModuleLoader/gradle.properties b/ModuleLoader/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/ModuleLoader/gradle/wrapper/gradle-wrapper.properties b/ModuleLoader/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b82aa23 --- /dev/null +++ b/ModuleLoader/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/ModuleLoader/settings.gradle b/ModuleLoader/settings.gradle new file mode 100644 index 0000000..423d429 --- /dev/null +++ b/ModuleLoader/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'ModuleLoader' \ No newline at end of file diff --git a/ModuleLoader/src/main/java/m/dict/Lock.java b/ModuleLoader/src/main/java/m/dict/Lock.java new file mode 100644 index 0000000..ec59d1c --- /dev/null +++ b/ModuleLoader/src/main/java/m/dict/Lock.java @@ -0,0 +1,4 @@ +package m.dict; + +public interface Lock { +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/ModuleLoader.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/ModuleLoader.java new file mode 100644 index 0000000..3ea561b --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/ModuleLoader.java @@ -0,0 +1,45 @@ +package opg.p2vman.moduleloader; + +import opg.p2vman.moduleloader.module.JavaModule; +import opg.p2vman.moduleloader.module.ModuleMannager; +import opg.p2vman.moduleloader.profiling.ExempleProfiler; +import opg.p2vman.moduleloader.profiling.Profiler; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.Optional; + +public final class ModuleLoader extends JavaPlugin { + private Optional loader = Optional.empty(); + @Override + public void onEnable() { + Profiler profiler = new ExempleProfiler(); + profiler.push("start_module_loader"); + try { + ModuleMannager moduleLoader = new ModuleMannager(); + File modules_dir = new File(getDataFolder(), "modules"); + if (!modules_dir.exists()) { + modules_dir.mkdirs(); + } + + moduleLoader.loadModules(modules_dir).forEach((javaModule -> { + javaModule.enable(); + System.out.printf("Module enabled %s", javaModule.getName()); + })); + + loader = Optional.of(moduleLoader); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + System.out.printf("modules loaded %dms%n", profiler.getElapsedTimeAndRemove(profiler.pop())); + } + + @Override + public void onDisable() { + JavaModule.MODULES.forEach(JavaModule::disable); + } + + public Optional getModuleLoader() { + return loader; + } +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/Identifier.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/Identifier.java new file mode 100644 index 0000000..a4d3ba0 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/Identifier.java @@ -0,0 +1,156 @@ +package opg.p2vman.moduleloader.api; + +public class Identifier implements Comparable { + public static final char NAMESPACE_SEPARATOR = ':'; + public static final String DEFAULT_NAMESPACE = "minecraft"; + private final String namespace; + private final String path; + public Identifier(String namespace, String path) { + this.namespace = namespace; + this.path = path; + } + private Identifier(String[] id) { + this(id[0], id[1]); + } + + public Identifier(String id) { + this(split(id, NAMESPACE_SEPARATOR)); + } + + public static Identifier splitOn(String id, char delimiter) { + return new Identifier(split(id, delimiter)); + } + + public static Identifier tryParse(String id) { + try { + return new Identifier(id); + } catch (RuntimeException var2) { + return null; + } + } + + public static Identifier of(String namespace, String path) { + try { + return new Identifier(namespace, path); + } catch (RuntimeException var3) { + return null; + } + } + + protected static String[] split(String id, char delimiter) { + String[] strings = new String[]{DEFAULT_NAMESPACE, id}; + int i = id.indexOf(delimiter); + if (i >= 0) { + strings[1] = id.substring(i + 1); + if (i >= 1) { + strings[0] = id.substring(0, i); + } + } + + return strings; + } + + public String getPath() { + return this.path; + } + + public String getNamespace() { + return this.namespace; + } + + public String toString() { + return this.namespace + NAMESPACE_SEPARATOR + this.path; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (!(o instanceof Identifier)) { + return false; + } else { + Identifier identifier = (Identifier)o; + return this.namespace.equals(identifier.namespace) && this.path.equals(identifier.path); + } + } + + public int hashCode() { + return 31 * this.namespace.hashCode() + this.path.hashCode(); + } + + public int compareTo(Identifier identifier) { + int i = this.path.compareTo(identifier.path); + if (i == 0) { + i = this.namespace.compareTo(identifier.namespace); + } + + return i; + } + + public String toUnderscoreSeparatedString() { + return this.toString().replace('/', '_').replace(NAMESPACE_SEPARATOR, '_'); + } + + public String toTranslationKey() { + return this.namespace + "." + this.path; + } + + public String toShortTranslationKey() { + return this.namespace.equals(DEFAULT_NAMESPACE) ? this.path : this.toTranslationKey(); + } + + public String toTranslationKey(String prefix) { + return prefix + "." + this.toTranslationKey(); + } + + public String toTranslationKey(String prefix, String suffix) { + return prefix + "." + this.toTranslationKey() + "." + suffix; + } + + public static boolean isCharValid(char c) { + return c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c == '_' || c == ':' || c == '/' || c == '.' || c == '-'; + } + + public static boolean isPathValid(String path) { + for(int i = 0; i < path.length(); ++i) { + if (!isPathCharacterValid(path.charAt(i))) { + return false; + } + } + + return true; + } + + public static boolean isNamespaceValid(String namespace) { + for(int i = 0; i < namespace.length(); ++i) { + if (!isNamespaceCharacterValid(namespace.charAt(i))) { + return false; + } + } + + return true; + } + + private static String validateNamespace(String namespace, String path) { + if (!isNamespaceValid(namespace)) { + throw new RuntimeException("Non [a-z0-9_.-] character in namespace of location: " + namespace + NAMESPACE_SEPARATOR + path); + } else { + return namespace; + } + } + + public static boolean isPathCharacterValid(char character) { + return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '/' || character == '.'; + } + + private static boolean isNamespaceCharacterValid(char character) { + return character == '_' || character == '-' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '.'; + } + + private static String validatePath(String namespace, String path) { + if (!isPathValid(path)) { + throw new RuntimeException("Non [a-z0-9/._-] character in path of location: " + namespace + NAMESPACE_SEPARATOR + path); + } else { + return path; + } + } +} \ No newline at end of file diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/Module.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/Module.java new file mode 100644 index 0000000..d568e18 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/Module.java @@ -0,0 +1,15 @@ +package opg.p2vman.moduleloader.api; + +import org.bukkit.Server; + +import java.util.logging.Logger; + +public interface Module { + void onLoad(); + void onEnable(); + void onDisable(); + String getName(); + ResourceMannager getResourceMannager(); + Server getServer(); + Logger getLogger(); +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/RemapedClass.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/RemapedClass.java new file mode 100644 index 0000000..5238c85 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/RemapedClass.java @@ -0,0 +1,12 @@ +package opg.p2vman.moduleloader.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RemapedClass { + String value(); +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/ResourceMannager.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/ResourceMannager.java new file mode 100644 index 0000000..f3e5873 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/ResourceMannager.java @@ -0,0 +1,13 @@ +package opg.p2vman.moduleloader.api; + +import java.io.InputStream; +import java.net.URL; + +public interface ResourceMannager { + InputStream getResourceAsStream(String name); + URL getResource(String name); + InputStream getResourceAsStream(Identifier identifier); + URL getResource(Identifier identifier); + URL findResource(final String name); + URL findResource(final Identifier identifier); +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/ServicesManager.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/ServicesManager.java new file mode 100644 index 0000000..4061f04 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/api/ServicesManager.java @@ -0,0 +1,21 @@ +package opg.p2vman.moduleloader.api; + +import java.util.HashMap; +import java.util.Map; + +public class ServicesManager { + + private final Map, Object> services = new HashMap<>(); + + public void registerService(Class serviceClass, T serviceInstance) { + services.put(serviceClass, serviceInstance); + } + + public T getService(Class serviceClass) { + return (T) services.get(serviceClass); + } + + public boolean hasService(Class serviceClass) { + return services.containsKey(serviceClass); + } +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/JavaModule.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/JavaModule.java new file mode 100644 index 0000000..b19d5d9 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/JavaModule.java @@ -0,0 +1,100 @@ +package opg.p2vman.moduleloader.module; + +import opg.p2vman.moduleloader.ModuleLoader; +import opg.p2vman.moduleloader.api.Module; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.List; + +public class JavaModule implements Module { + private boolean isEnabled = false; + public static final List MODULES = new ArrayList<>(); + private ModuleContainer container; + private ModuleResourceMannager resourceMannager; + private ModuleLogger logger; + public JavaModule() { + + } + + + public final void init(ModuleContainer container, ModuleResourceMannager resourceMannager, ModuleLogger logger) { + for (JavaModule module : MODULES) if (this.getClass().isInstance(module)) { + throw new RuntimeException(); + } + this.resourceMannager = resourceMannager; + this.container = container; + this.logger = logger; + MODULES.add(this); + } + + public static T getModule(Class cls) { + for (JavaModule module : MODULES) if (cls.isInstance(module)) { + return (T) module; + } + return null; + } + + @Override + public String getName() { + return container.meta.name; + } + + @Override + public void onDisable() { + } + + @Override + public void onEnable() { + + } + + @Override + public void onLoad() { + + } + + public void enable() { + if (isEnabled) throw new RuntimeException(); + try { + this.onEnable(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + isEnabled = true; + } + } + + public void disable() { + if (!isEnabled) throw new RuntimeException(); + try { + this.onDisable(); + MODULES.remove(this); + } catch (Exception e) { + e.printStackTrace(); + } finally { + isEnabled = false; + } + } + + @Override + public ModuleResourceMannager getResourceMannager() { + return resourceMannager; + } + + @Override + public Server getServer() { + return Bukkit.getServer(); + } + + @Override + public ModuleLogger getLogger() { + return logger; + } + + public JavaPlugin getPlugin() { + return ModuleLoader.getPlugin(ModuleLoader.class); + } +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleContainer.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleContainer.java new file mode 100644 index 0000000..e2f0cf3 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleContainer.java @@ -0,0 +1,13 @@ +package opg.p2vman.moduleloader.module; + +import java.io.File; + +public class ModuleContainer { + public final File module_file; + public final ModuleMeta meta; + + public ModuleContainer(ModuleMeta meta, File module_file) { + this.meta = meta; + this.module_file = module_file; + } +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleLogger.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleLogger.java new file mode 100644 index 0000000..8d43fcc --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleLogger.java @@ -0,0 +1,30 @@ +package opg.p2vman.moduleloader.module; + +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +public class ModuleLogger extends Logger { + private String name; + + /** + * Creates a new ModuleLogger that extracts the name from a module. + * + * @param context A reference to the module + */ + public ModuleLogger(@NotNull ModuleContainer context) { + super(context.getClass().getCanonicalName(), null); + name = new StringBuilder().append("[").append(context.meta.name).append("] ").toString(); + setParent(Bukkit.getServer().getLogger()); + setLevel(Level.ALL); + } + + @Override + public void log(@NotNull LogRecord logRecord) { + logRecord.setMessage(name + logRecord.getMessage()); + super.log(logRecord); + } +} \ No newline at end of file diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleMannager.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleMannager.java new file mode 100644 index 0000000..06e1386 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleMannager.java @@ -0,0 +1,178 @@ +package opg.p2vman.moduleloader.module; + +import com.google.gson.Gson; +import m.dict.Lock; +import org.objectweb.asm.*; + +import java.io.*; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.security.SecureRandom; +import java.util.*; +import java.util.function.Supplier; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class ModuleMannager { + public static final FilenameFilter MODULEFILTER = (File dir, String name) -> name.endsWith(".jar") || name.endsWith(".zip") || name.endsWith(".ear"); + private final MethodHandles.Lookup lookup; + private final Gson GSON; + private final SecureRandom random = new SecureRandom(); + private final Map classNameMap = new HashMap<>(); + + public ModuleMannager() throws IllegalAccessException { + lookup = MethodHandles.privateLookupIn(Lock.class, MethodHandles.lookup()); + GSON = new Gson(); + } + + public List loadModules(File dir) { + List modules = new ArrayList<>(); + for (File file : Objects.requireNonNull(dir.listFiles(MODULEFILTER))) { + try { + modules.add(loadModule(file).get()); + } catch (Exception e) { + e.printStackTrace(); + } + } + return modules; + } + + public Supplier loadModule(File plugin) { + synchronized (lookup) { + classNameMap.clear(); + List> classes = new ArrayList<>(); + Optional module = Optional.empty(); + + try (JarFile jarFile = new JarFile(plugin)) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + try (InputStream is = jarFile.getInputStream(entry)) { + Class cls = transformClass(is.readAllBytes()); + classes.add(cls); + System.out.println("Loaded: " + cls.getName()); + } catch (Exception e) { + throw new AssertionError(e); + } + } + } + + try (InputStream is = jarFile.getInputStream(jarFile.getEntry("module.json"))) { + ModuleMeta meta = GSON.fromJson(new InputStreamReader(is), ModuleMeta.class); + ModuleContainer container = new ModuleContainer(meta, plugin); + ModuleResourceMannager resourceMannager = new ModuleResourceMannager(container); + ModuleLogger logger = new ModuleLogger(container); + + String newMainClass = classNameMap.get(container.meta.main_class.replace('.', '/')).replace('/', '.'); + JavaModule module1 = (JavaModule) lookup.findConstructor(lookup.findClass(newMainClass), MethodType.methodType(void.class)).invoke(); + module1.init(container, resourceMannager, logger); + + module = Optional.of(module1); + } catch (Throwable e) { + throw new AssertionError(e); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + final JavaModule module1 = module.orElseThrow(); + return () -> { + module1.onLoad(); + return module1; + }; + } + } + + private final Map> classCache = new HashMap<>(); + + private Class transformClass(byte[] classBytes) { + ClassReader reader = new ClassReader(classBytes); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + + String oldName = reader.getClassName(); + String newName = "m/dict/" + generateRandomName(); + + while (classCache.containsKey(newName)) { + newName = "m/dict/" + generateRandomName(); + } + + classNameMap.put(oldName, newName); + + final String finalnewName = newName; + reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, finalnewName, signature, superName, interfaces); + + AnnotationVisitor annotationVisitor = super.visitAnnotation("Lopg/eptalist/moduleloader/api/RemapedClass;", true); + annotationVisitor.visit("value", oldName); + annotationVisitor.visitEnd(); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + return super.visitField(access, name, updateDescriptor(descriptor), signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, updateDescriptor(descriptor), signature, exceptions); + return new MethodVisitor(Opcodes.ASM9, mv) { + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + super.visitFieldInsn(opcode, remapClass(owner), name, updateDescriptor(descriptor)); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + super.visitMethodInsn(opcode, remapClass(owner), name, updateDescriptor(descriptor), isInterface); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, remapClass(type)); + } + + @Override + public void visitLdcInsn(Object value) { + if (value instanceof String && classNameMap.containsKey(value)) { + value = classNameMap.get(value); + } + super.visitLdcInsn(value); + } + }; + } + }, 0); + + byte[] transformedBytes = writer.toByteArray(); + + if (!classCache.containsKey(newName)) { + try { + classCache.put(newName, lookup.defineClass(transformedBytes)); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + return classCache.get(newName); + } + + private String remapClass(String className) { + return classNameMap.getOrDefault(className, className); + } + + private String updateDescriptor(String descriptor) { + if (descriptor == null) return null; + for (Map.Entry entry : classNameMap.entrySet()) { + descriptor = descriptor.replace("L" + entry.getKey() + ";", "L" + entry.getValue() + ";"); + } + return descriptor; + } + + private String generateRandomName() { + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleMeta.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleMeta.java new file mode 100644 index 0000000..68df0b7 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleMeta.java @@ -0,0 +1,10 @@ +package opg.p2vman.moduleloader.module; + +import java.util.List; +import java.util.Map; + +public class ModuleMeta { + public String main_class; + public String name; + public Map> class_lookups; +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleResourceMannager.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleResourceMannager.java new file mode 100644 index 0000000..68f4984 --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/module/ModuleResourceMannager.java @@ -0,0 +1,31 @@ +package opg.p2vman.moduleloader.module; + +import opg.p2vman.moduleloader.api.Identifier; +import opg.p2vman.moduleloader.api.ResourceMannager; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +public class ModuleResourceMannager extends URLClassLoader implements ResourceMannager { + private ModuleContainer container; + public ModuleResourceMannager(ModuleContainer container) throws MalformedURLException { + super(new URL[]{container.module_file.toURI().toURL()}); + this.container = container; + } + @Override + public URL getResource(Identifier identifier) { + return getResource(String.format("assets/%s/%s", identifier.getNamespace(), identifier.getPath())); + } + + @Override + public InputStream getResourceAsStream(Identifier identifier) { + return getResourceAsStream(String.format("assets/%s/%s", identifier.getNamespace(), identifier.getPath())); + } + + @Override + public URL findResource(Identifier identifier) { + return findResource(String.format("assets/%s/%s", identifier.getNamespace(), identifier.getPath())); + } +} diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/profiling/ExempleProfiler.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/profiling/ExempleProfiler.java new file mode 100644 index 0000000..3d03c9d --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/profiling/ExempleProfiler.java @@ -0,0 +1,53 @@ +package opg.p2vman.moduleloader.profiling; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +public class ExempleProfiler implements Profiler { + private final Map totalTimes = new HashMap<>(); + private final Stack stack = new Stack<>(); + private final Map startTimes = new HashMap<>(); + + public void push(String name) { + stack.push(name); + startTimes.put(name, System.nanoTime()); + } + + public String pop() { + if (stack.isEmpty()) { + throw new IllegalStateException("Нет активных блоков для остановки."); + } + String name = stack.pop(); + Long startTime = startTimes.remove(name); + if (startTime == null) { + throw new IllegalStateException("Блок " + name + " не был запущен."); + } + long elapsedTime = System.nanoTime() - startTime; + totalTimes.put(name, totalTimes.getOrDefault(name, 0L) + elapsedTime); + return name; + } + + public String peek() { + if (stack.isEmpty()) { + throw new IllegalStateException("Нет активных блоков."); + } + return stack.peek(); + } + + public long getElapsedTimeAndRemove(String name) { + Long elapsedTime = totalTimes.remove(name); + if (elapsedTime == null) { + throw new IllegalStateException("Блок " + name + " не найден."); + } + return elapsedTime / 1_000_000; + } + + public long getElapsedTime(String name) { + Long elapsedTime = totalTimes.get(name); + if (elapsedTime == null) { + throw new IllegalStateException("Блок " + name + " не найден."); + } + return elapsedTime / 1_000_000; + } +} \ No newline at end of file diff --git a/ModuleLoader/src/main/java/opg/p2vman/moduleloader/profiling/Profiler.java b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/profiling/Profiler.java new file mode 100644 index 0000000..d9dd5af --- /dev/null +++ b/ModuleLoader/src/main/java/opg/p2vman/moduleloader/profiling/Profiler.java @@ -0,0 +1,9 @@ +package opg.p2vman.moduleloader.profiling; + +public interface Profiler { + void push(String name); + String pop(); + String peek(); + long getElapsedTimeAndRemove(String name); + long getElapsedTime(String name); +} \ No newline at end of file diff --git a/ModuleLoader/src/main/resources/plugin.yml b/ModuleLoader/src/main/resources/plugin.yml new file mode 100644 index 0000000..70cb320 --- /dev/null +++ b/ModuleLoader/src/main/resources/plugin.yml @@ -0,0 +1,4 @@ +name: ModuleLoader +version: '${version}' +main: opg.p2vman.moduleloader.ModuleLoader +api-version: '1.18' diff --git a/settings.gradle b/settings.gradle index 14f2595..155d70b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,7 @@ rootProject.name = 'EptaListProject' -include ':base', ':velocity', ':spigot', ":boungecord" \ No newline at end of file +include ':base', ':velocity', ':spigot', ":boungecord" +include 'ModuleLoader:ExempleModule' +findProject(':ModuleLoader:ExempleModule')?.name = 'ExempleModule' +include 'ModuleLoader:Exemplemodule' +findProject(':ModuleLoader:Exemplemodule')?.name = 'Exemplemodule' +