From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> Date: Wed, 12 Oct 2022 10:42:15 -0400 Subject: [PATCH] Leaf Config Leaf Config v3 including load config, backup old or outdated config, and add config to spark profiler automatically. TODO - Dreeam: Add per world config Add config reload diff --git a/build.gradle.kts b/build.gradle.kts index a79693afc8c21448a4134f730eff910e1b88e2a8..ca9f19c451155e3b411171f757ed9feed2a43a58 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,13 @@ val alsoShade: Configuration by configurations.creating dependencies { implementation(project(":leaf-api")) // Gale start - project setup - Depend on own API // Leaf + + // Leaf start - Leaf Config + implementation("com.github.thatsmusic99:ConfigurationMaster-API:v2.0.0-rc.2") { + exclude(group = "org.yaml", module = "snakeyaml") + } + // Leaf end - Leaf Config + // Paper start implementation("org.jline:jline-terminal-jansi:3.21.0") implementation("net.minecrell:terminalconsoleappender:1.3.0") diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index 278adb48400ca9d4fd37bff040b37d4a8dd47282..759b22fc6f949829cef757232357368ef80d0d34 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -124,6 +124,7 @@ public class Main { Bootstrap.bootStrap(); Bootstrap.validate(); Util.startTimerHackThread(); + org.dreeam.leaf.config.LeafConfig.loadConfig(); // Leaf Path path1 = Paths.get("server.properties"); DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index f4dd63ae1a9715f3cd10949ca49a269897443e56..1e18635c78ee73fab352465e56f2dc827ae8337a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1222,6 +1222,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop modules = new HashSet<>(); + public LeafGlobalConfig config; + + public ConfigModules() { + this.config = LeafConfig.config(); + } + + public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + for (Class clazz : LeafConfig.getClasses(LeafConfig.I_CONFIG_PKG)) { + ConfigModules module = (ConfigModules) clazz.getConstructor().newInstance(); + module.onLoaded(); + + modules.add(module); + } + } + + public abstract void onLoaded(); +} diff --git a/src/main/java/org/dreeam/leaf/config/DoNotLoad.java b/src/main/java/org/dreeam/leaf/config/DoNotLoad.java new file mode 100644 index 0000000000000000000000000000000000000000..42ce82d388336906a91547b81f6f70766f2b10f0 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/config/DoNotLoad.java @@ -0,0 +1,8 @@ +package org.dreeam.leaf.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface DoNotLoad { +} diff --git a/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java b/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java new file mode 100644 index 0000000000000000000000000000000000000000..7d99f0711c6b4f298dd296c26c11dd9d4b633264 --- /dev/null +++ b/src/main/java/org/dreeam/leaf/config/EnumConfigCategory.java @@ -0,0 +1,26 @@ +package org.dreeam.leaf.config; + +public enum EnumConfigCategory { + + ASYNC("async"), + PERF("performance"), + FIXES("fixes"), + GAMEPLAY("gameplay-mechanisms"), + NETWORK("network"), + MISC("misc"); + + private final String baseKeyName; + private static final EnumConfigCategory[] VALUES = EnumConfigCategory.values(); + + EnumConfigCategory(String baseKeyName) { + this.baseKeyName = baseKeyName; + } + + public String getBaseKeyName() { + return this.baseKeyName; + } + + public static EnumConfigCategory[] getCategoryValues() { + return VALUES; + } +} diff --git a/src/main/java/org/dreeam/leaf/config/HotReloadUnsupported.java b/src/main/java/org/dreeam/leaf/config/HotReloadUnsupported.java new file mode 100644 index 0000000000000000000000000000000000000000..f7ab1ff5f298ff1e5e16fe5396d1e9e62a55fdfb --- /dev/null +++ b/src/main/java/org/dreeam/leaf/config/HotReloadUnsupported.java @@ -0,0 +1,8 @@ +package org.dreeam.leaf.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface HotReloadUnsupported { +} diff --git a/src/main/java/org/dreeam/leaf/config/LeafConfig.java b/src/main/java/org/dreeam/leaf/config/LeafConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..2135969dba99c162041f4e8c53624f15dc4e992d --- /dev/null +++ b/src/main/java/org/dreeam/leaf/config/LeafConfig.java @@ -0,0 +1,232 @@ +package org.dreeam.leaf.config; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; + +/* + * Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol + * @author: @xGinko & @MrHua269 + */ +public class LeafConfig { + + public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName()); + protected static final File I_CONFIG_FOLDER = new File("config"); + protected static final String I_CONFIG_PKG = "org.dreeam.leaf.config.modules"; + protected static final String I_GLOBAL_CONFIG_FILE = "leaf-global.yml"; + protected static final String I_LEVEL_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per level config + + private static LeafGlobalConfig leafGlobalConfig; + + /* Load & Reload */ + + public static void reload() { + try { + long begin = System.nanoTime(); + LOGGER.info("Reloading config..."); + + loadConfig(false); + + LOGGER.info("Successfully reloaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); + } catch (Exception e) { + LOGGER.error("Failed to reload config.", e); + } + } + + @Contract(" -> new") + public static @NotNull CompletableFuture reloadAsync() { + return new CompletableFuture<>(); + } + + public static void loadConfig() { + try { + long begin = System.nanoTime(); + LOGGER.info("Loading config..."); + + purgeOutdated(); + loadConfig(true); + + LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000); + } catch (Exception e) { + LeafConfig.LOGGER.error("Failed to load config modules!", e); + } + } + + /* Load Global Config */ + + private static void loadConfig(boolean init) throws Exception { + // Create config folder + createDirectory(LeafConfig.I_CONFIG_FOLDER); + + leafGlobalConfig = new LeafGlobalConfig(init); + + // Load config modules + ConfigModules.initModules(); + + // Save config to disk + leafGlobalConfig.saveConfig(); + } + + public static LeafGlobalConfig config() { + return leafGlobalConfig; + } + + /* Create config folder */ + + protected static void createDirectory(File dir) throws IOException { + try { + Files.createDirectories(dir.toPath()); + } catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory + if (dir.delete()) createDirectory(dir); + } + } + + /* Scan classes under package */ + + public static @NotNull Set> getClasses(String pack) { + Set> classes = new LinkedHashSet<>(); + String packageDirName = pack.replace('.', '/'); + Enumeration dirs; + + try { + dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8); + findClassesInPackageByFile(pack, filePath, classes); + } else if ("jar".equals(protocol)) { + JarFile jar; + try { + jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + findClassesInPackageByJar(pack, entries, packageDirName, classes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + return classes; + } + + private static void findClassesInPackageByFile(String packageName, String packagePath, Set> classes) { + File dir = new File(packagePath); + + if (!dir.exists() || !dir.isDirectory()) { + return; + } + + File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class")); + if (dirfiles != null) { + for (File file : dirfiles) { + if (file.isDirectory()) { + findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes); + } else { + String className = file.getName().substring(0, file.getName().length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + } + } + + private static void findClassesInPackageByJar(String packageName, Enumeration entries, String packageDirName, Set> classes) { + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (name.charAt(0) == '/') { + name = name.substring(1); + } + + if (name.startsWith(packageDirName)) { + int idx = name.lastIndexOf('/'); + + if (idx != -1) { + packageName = name.substring(0, idx).replace('/', '.'); + } + + if (name.endsWith(".class") && !entry.isDirectory()) { + String className = name.substring(packageName.length() + 1, name.length() - 6); + try { + classes.add(Class.forName(packageName + '.' + className)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + } + } + + /* Register Spark profiler extra server configurations */ + + public static void regSparkExtraConfig() { + if (Bukkit.getServer().getPluginManager().getPlugin("spark") != null) { + System.setProperty("spark.serverconfigs.extra", "config/leaf-global.yml"); + } + } + + /* Purge and backup old Leaf config & Pufferfish config */ + + private static void purgeOutdated() { + String pufferfishConfig = "pufferfish.yml"; + String leafConfigV1 = "leaf.yml"; + String leafConfigV2 = "leaf_config"; + + Date date = new Date(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss"); + String backupDir = "config/backup" + dateFormat.format(date) + "/"; + + File pufferfishConfigFile = new File(pufferfishConfig); + File leafConfigV1File = new File(leafConfigV1); + File leafConfigV2File = new File(leafConfigV2); + File backupDirFile = new File(backupDir); + + try { + if (pufferfishConfigFile.exists() && pufferfishConfigFile.isFile()) { + createDirectory(backupDirFile); + Files.move(pufferfishConfigFile.toPath(), Path.of(backupDir + pufferfishConfig), StandardCopyOption.REPLACE_EXISTING); + } + if (leafConfigV1File.exists() && leafConfigV1File.isFile()) { + createDirectory(backupDirFile); + Files.move(leafConfigV1File.toPath(), Path.of(backupDir + leafConfigV1), StandardCopyOption.REPLACE_EXISTING); + } + if (leafConfigV2File.exists() && leafConfigV2File.isDirectory()) { + createDirectory(backupDirFile); + Files.move(leafConfigV2File.toPath(), Path.of(backupDir + leafConfigV2), StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException e) { + LOGGER.error("Failed to purge old configs.", e); + } + } +} diff --git a/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java b/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6685386a36c469827c646ce9cea914de2b4d531b --- /dev/null +++ b/src/main/java/org/dreeam/leaf/config/LeafGlobalConfig.java @@ -0,0 +1,111 @@ +package org.dreeam.leaf.config; + +import io.github.thatsmusic99.configurationmaster.api.ConfigFile; +import io.github.thatsmusic99.configurationmaster.api.ConfigSection; + +import java.io.File; +import java.util.List; +import java.util.Map; + +public class LeafGlobalConfig { + + protected static ConfigFile configFile; + + public LeafGlobalConfig(boolean init) throws Exception { + configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE)); + configFile.set("config-version", 3.0); + configFile.addComments("config-version", """ + Leaf Config + Github Repo: https://github.com/Winds-Studio/Leaf + Discord: https://discord.com/invite/gfgAwdSEuM + QQ Group: 715128273"""); + + // Pre-structure to force order + structureConfig(); + } + + protected void structureConfig() { + for (EnumConfigCategory configCate : EnumConfigCategory.getCategoryValues()) { + createTitledSection(configCate.name(), configCate.getBaseKeyName()); + } + } + + public void saveConfig() throws Exception { + configFile.save(); + } + + // Config Utilities + + public void createTitledSection(String title, String path) { + configFile.addSection(title); + configFile.addDefault(path, null); + } + + public boolean getBoolean(String path, boolean def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getBoolean(path, def); + } + + public boolean getBoolean(String path, boolean def) { + configFile.addDefault(path, def); + return configFile.getBoolean(path, def); + } + + public String getString(String path, String def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getString(path, def); + } + + public String getString(String path, String def) { + configFile.addDefault(path, def); + return configFile.getString(path, def); + } + + public double getDouble(String path, double def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getDouble(path, def); + } + + public double getDouble(String path, double def) { + configFile.addDefault(path, def); + return configFile.getDouble(path, def); + } + + public int getInt(String path, int def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getInteger(path, def); + } + + public int getInt(String path, int def) { + configFile.addDefault(path, def); + return configFile.getInteger(path, def); + } + + public List getList(String path, List def, String comment) { + configFile.addDefault(path, def, comment); + return configFile.getStringList(path); + } + + public List getList(String path, List def) { + configFile.addDefault(path, def); + return configFile.getStringList(path); + } + + public ConfigSection getConfigSection(String path, Map defaultKeyValue) { + configFile.addDefault(path, null); + configFile.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) { + configFile.addDefault(path, null, comment); + configFile.makeSectionLenient(path); + defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object)); + return configFile.getConfigSection(path); + } + + public void addComment(String path, String comment) { + configFile.addComment(path, comment); + } +}