From 2c7dfaaaf9bd983b4b1020ca7f7a53c64549b53f Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Aug 2018 19:25:36 +0300 Subject: [PATCH 1/6] Allow config upgrading. Add annotations with reflection --- .../velocitypowered/proxy/VelocityServer.java | 19 +- .../proxy/config/AnnotationConfig.java | 179 +++++++++++ .../proxy/config/VelocityConfiguration.java | 277 ++++++++++++------ 3 files changed, 374 insertions(+), 101 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 13fccef60..13903c3a8 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -21,6 +21,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.proxy.command.VelocityCommandManager; +import com.velocitypowered.proxy.config.AnnotationConfig; import com.velocitypowered.proxy.messages.VelocityChannelRegistrar; import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.protocol.util.FaviconSerializer; @@ -50,6 +51,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; public class VelocityServer implements ProxyServer { + private static final Logger logger = LogManager.getLogger(VelocityServer.class); public static final Gson GSON = new GsonBuilder() .registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer()) @@ -106,19 +108,16 @@ public class VelocityServer implements ProxyServer { public void start() { try { Path configPath = Paths.get("velocity.toml"); - try { - configuration = VelocityConfiguration.read(configPath); - } catch (NoSuchFileException e) { - logger.info("No velocity.toml found, creating one for you..."); - Files.copy(VelocityServer.class.getResourceAsStream("/velocity.toml"), configPath); - configuration = VelocityConfiguration.read(configPath); - } + configuration = VelocityConfiguration.read(configPath); if (!configuration.validate()) { logger.error("Your configuration is invalid. Velocity will refuse to start up until the errors are resolved."); System.exit(1); } - } catch (IOException e) { + + AnnotationConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values + + } catch (IOException | NullPointerException e) { logger.error("Unable to load your velocity.toml. The server will shut down.", e); System.exit(1); } @@ -192,7 +191,9 @@ public class VelocityServer implements ProxyServer { } public void shutdown() { - if (!shutdownInProgress.compareAndSet(false, true)) return; + if (!shutdownInProgress.compareAndSet(false, true)) { + return; + } logger.info("Shutting down the proxy..."); for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java new file mode 100644 index 000000000..6e83c2828 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java @@ -0,0 +1,179 @@ +package com.velocitypowered.proxy.config; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Only for simple configs + */ +public class AnnotationConfig { + + private static final Logger logger = LogManager.getLogger(AnnotationConfig.class); + + public static Logger getLogger() { + return logger; + } + + /** + * Indicates that a field is a table + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + public @interface Table { + + String value(); + } + + /** + * Creates a comment + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface Comment { + + String[] value(); + } + + /** + * How field will be named in config + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface CfgKey { + + String value(); + } + + /** + * Indicates that a field is map and we need to save all data to config + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface AsMap { + } + + /** + * Indicates that a field is a string converted to byte[] + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface AsBytes { + } + + /** + * Indicates that a field is a string converted to byte[] + */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD, ElementType.TYPE}) + public @interface Ignore { + } + + public List dumpConfig() { + List lines = new ArrayList<>(); + dumpFields(getClass(), this, lines); + return lines; + } + + private void dumpFields(Class root, Object caller, List lines) { + + try { + for (Field field : root.getDeclaredFields()) { + if (field.getAnnotation(Ignore.class) != null) { + continue; + } + Comment comment = field.getAnnotation(Comment.class); + if (comment != null) { //Add comments + for (String line : comment.value()) { + lines.add("# " + line); + } + } + CfgKey key = field.getAnnotation(CfgKey.class); + String name = key == null ? field.getName() : key.value(); + field.setAccessible(true); + Table table = field.getAnnotation(Table.class); + if (table != null) { + lines.add(table.value()); // Write [name] + dumpFields(field.getType(), field.get(caller), lines); // dump a table class + } else { + if (field.getAnnotation(AsMap.class) != null) { + Map map = (Map) field.get(caller); + for (Entry entry : map.entrySet()) { + lines.add(entry.getKey() + " = " + toString(entry.getValue())); + } + lines.add(""); + continue; + } + Object value = field.get(caller); + if (field.getAnnotation(AsBytes.class) != null) { + value = new String((byte[]) value, StandardCharsets.UTF_8); + } + lines.add(name + " = " + toString(value)); + lines.add(""); + } + } + } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { + logger.log(Level.ERROR, "Unexpected error while dumping fields", e); + lines.clear(); + } + } + + private String toString(Object value) { + if (value instanceof List) { + Collection listValue = (Collection) value; + if (listValue.isEmpty()) { + return "[]"; + } + StringBuilder m = new StringBuilder(); + m.append("["); + for (Object obj : listValue) { + m.append(System.lineSeparator()).append(" ").append(toString(obj)).append(","); + } + m.deleteCharAt(m.length() - 1).append(System.lineSeparator()).append("]"); + return m.toString(); + } + if (value instanceof Enum) { + value = value.toString(); + } + if (value instanceof String) { + String stringValue = (String) value; + if (stringValue.isEmpty()) { + return "\"\""; + } + return "\"" + stringValue + "\""; + } + return value != null ? value.toString() : "null"; + } + + public static void saveConfig(List lines, Path to) throws IOException { + if (lines.isEmpty()) { + throw new IOException("Can not save config because list is empty"); + } + Path temp = new File(to.toFile().getParent(), "__tmp").toPath(); + Files.write(temp, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE); + try { + Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException e) { + Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING); + } + + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 5843cb59a..66a374d0b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -5,11 +5,10 @@ import com.moandjiezana.toml.Toml; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.api.util.LegacyChatColorUtils; +import com.velocitypowered.proxy.VelocityServer; import io.netty.buffer.ByteBufUtil; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import java.io.IOException; import java.io.Reader; @@ -21,51 +20,140 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.Logger; -public class VelocityConfiguration { - private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class); +public class VelocityConfiguration extends AnnotationConfig { + @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") private final String bind; + @Comment("What should be the MOTD? Legacy color codes and JSON are accepted.") private final String motd; + @Comment({"What should we display for the maximum number of players? (Velocity does not support a cap", + "on the number of players online.)"}) + @CfgKey("show-max-players") private final int showMaxPlayers; + @Comment("Should we authenticate players with Mojang? By default, this is on.") + @CfgKey("online-mode") private final boolean onlineMode; + @Comment({"Should we forward IP addresses and other data to backend servers?", + "Available options:", + "- \"none\": No forwarding will be done. All players will appear to be Should we forward IP addresses and other data to backend servers?connecting from the proxy", + " and will have offline-mode UUIDs.", + "- \"legacy\": Forward player IPs and UUIDs in BungeeCord-compatible fashion. Use this if you run", + " servers using Minecraft 1.12 or lower.", + "- \"modern\": Forward player IPs and UUIDs as part of the login process using Velocity's native", + " forwarding. Only applicable for Minecraft 1.13 or higher."}) + @CfgKey("player-info-forwarding-mode") private final PlayerInfoForwarding playerInfoForwardingMode; - private final Map servers; - private final List attemptConnectionOrder; - private final int compressionThreshold; - private final int compressionLevel; - private final int loginRatelimit; - - private final boolean queryEnabled; - private final int queryPort; - - private Component motdAsComponent; - private Favicon favicon; + @AsBytes + @Comment("If you are using modern IP forwarding, configure an unique secret here.") + @CfgKey("forwarding-secret") private final byte[] forwardingSecret; + @Table("[servers]") + private final Servers servers; + + private static class Servers { + + @AsMap + @Comment("Configure your servers here.") + public final Map servers; + + @Comment("In what order we should try servers when a player logs in or is kicked from a server.") + @CfgKey("try") + public final List attemptConnectionOrder; + + public Servers(Map servers, List attemptConnectionOrder) { + this.servers = servers; + this.attemptConnectionOrder = attemptConnectionOrder; + } + + @Override + public String toString() { + return "Servers{" + "servers=" + servers + ", attemptConnectionOrder=" + attemptConnectionOrder + '}'; + } + + } + + @Table("[advanced]") + private final Advanced advanced; + + private static class Advanced { + + @Comment({"How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", + "setting it to -1 will disable compression entirely."}) + @CfgKey("compression-threshold") + public final int compressionThreshold; + @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") + @CfgKey("compression-level") + public final int compressionLevel; + @Comment({"How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", + "Disable by setting to 0"}) + @CfgKey("login-ratelimit") + public final int loginRatelimit; + + public Advanced(Toml toml) { + this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); + this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); + this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); + } + + @Override + public String toString() { + return "Advanced{" + "compressionThreshold=" + compressionThreshold + ", compressionLevel=" + compressionLevel + ", loginRatelimit=" + loginRatelimit + '}'; + } + } + + @Table("[query]") + private final Query query; + + private static class Query { + + @Comment("Whether to enable responding to GameSpy 4 query responses or not") + @CfgKey("enabled") + public final boolean queryEnabled; + @Comment("If query responding is enabled, on what port should query response listener listen on?") + @CfgKey("port") + public final int queryPort; + + public Query(boolean queryEnabled, int queryPort) { + this.queryEnabled = queryEnabled; + this.queryPort = queryPort; + } + + private Query(Toml toml) { + this.queryEnabled = toml.getBoolean("enabled", false); + this.queryPort = toml.getLong("port", 25577L).intValue(); + } + + @Override + public String toString() { + return "Query{" + "queryEnabled=" + queryEnabled + ", queryPort=" + queryPort + '}'; + } + } + @Ignore + private Component motdAsComponent; + @Ignore + private Favicon favicon; + private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, - PlayerInfoForwarding playerInfoForwardingMode, Map servers, - List attemptConnectionOrder, int compressionThreshold, - int compressionLevel, int loginRatelimit, boolean queryEnabled, - int queryPort, byte[] forwardingSecret) { + PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, Servers servers, + Advanced advanced, Query query) { this.bind = bind; this.motd = motd; this.showMaxPlayers = showMaxPlayers; this.onlineMode = onlineMode; this.playerInfoForwardingMode = playerInfoForwardingMode; - this.servers = servers; - this.attemptConnectionOrder = attemptConnectionOrder; - this.compressionThreshold = compressionThreshold; - this.compressionLevel = compressionLevel; - this.loginRatelimit = loginRatelimit; - this.queryEnabled = queryEnabled; - this.queryPort = queryPort; this.forwardingSecret = forwardingSecret; + this.servers = servers; + this.advanced = advanced; + this.query = query; } public boolean validate() { boolean valid = true; + Logger logger = AnnotationConfig.getLogger(); if (bind.isEmpty()) { logger.error("'bind' option is empty."); @@ -95,16 +183,16 @@ public class VelocityConfiguration { break; } - if (servers.isEmpty()) { + if (servers.servers.isEmpty()) { logger.error("You have no servers configured. :("); valid = false; } else { - if (attemptConnectionOrder.isEmpty()) { + if (servers.attemptConnectionOrder.isEmpty()) { logger.error("No fallback servers are configured!"); valid = false; } - for (Map.Entry entry : servers.entrySet()) { + for (Map.Entry entry : servers.servers.entrySet()) { try { AddressUtil.parseAddress(entry.getValue()); } catch (IllegalArgumentException e) { @@ -113,8 +201,8 @@ public class VelocityConfiguration { } } - for (String s : attemptConnectionOrder) { - if (!servers.containsKey(s)) { + for (String s : servers.attemptConnectionOrder) { + if (!servers.servers.containsKey(s)) { logger.error("Fallback server " + s + " doesn't exist!"); valid = false; } @@ -128,22 +216,22 @@ public class VelocityConfiguration { valid = false; } - if (compressionLevel < -1 || compressionLevel > 9) { - logger.error("Invalid compression level {}", compressionLevel); + if (advanced.compressionLevel < -1 || advanced.compressionLevel > 9) { + logger.error("Invalid compression level {}", advanced.compressionLevel); valid = false; - } else if (compressionLevel == 0) { + } else if (advanced.compressionLevel == 0) { logger.warn("ALL packets going through the proxy are going to be uncompressed. This will increase bandwidth usage."); } - if (compressionThreshold < -1) { - logger.error("Invalid compression threshold {}", compressionLevel); + if (advanced.compressionThreshold < -1) { + logger.error("Invalid compression threshold {}", advanced.compressionLevel); valid = false; - } else if (compressionThreshold == 0) { + } else if (advanced.compressionThreshold == 0) { logger.warn("ALL packets going through the proxy are going to be compressed. This may hurt performance."); } - if (loginRatelimit < 0) { - logger.error("Invalid login ratelimit {}", loginRatelimit); + if (advanced.loginRatelimit < 0) { + logger.error("Invalid login ratelimit {}", advanced.loginRatelimit); valid = false; } @@ -158,7 +246,7 @@ public class VelocityConfiguration { try { this.favicon = Favicon.create(faviconPath); } catch (Exception e) { - logger.info("Unable to load your server-icon.png, continuing without it.", e); + getLogger().info("Unable to load your server-icon.png, continuing without it.", e); } } } @@ -168,11 +256,11 @@ public class VelocityConfiguration { } public boolean isQueryEnabled() { - return queryEnabled; + return query.queryEnabled; } public int getQueryPort() { - return queryPort; + return query.queryPort; } public String getMotd() { @@ -203,23 +291,23 @@ public class VelocityConfiguration { } public Map getServers() { - return servers; + return servers.servers; } public List getAttemptConnectionOrder() { - return attemptConnectionOrder; + return servers.attemptConnectionOrder; } public int getCompressionThreshold() { - return compressionThreshold; + return advanced.compressionThreshold; } public int getCompressionLevel() { - return compressionLevel; + return advanced.compressionLevel; } public int getLoginRatelimit() { - return loginRatelimit; + return advanced.loginRatelimit; } public Favicon getFavicon() { @@ -232,57 +320,62 @@ public class VelocityConfiguration { @Override public String toString() { - return "VelocityConfiguration{" + - "bind='" + bind + '\'' + - ", motd='" + motd + '\'' + - ", showMaxPlayers=" + showMaxPlayers + - ", onlineMode=" + onlineMode + - ", playerInfoForwardingMode=" + playerInfoForwardingMode + - ", servers=" + servers + - ", attemptConnectionOrder=" + attemptConnectionOrder + - ", compressionThreshold=" + compressionThreshold + - ", compressionLevel=" + compressionLevel + - ", loginRatelimit=" + loginRatelimit + - ", queryEnabled=" + queryEnabled + - ", queryPort=" + queryPort + - ", motdAsComponent=" + motdAsComponent + - ", favicon=" + favicon + - ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) + - '}'; + + return "VelocityConfiguration{" + + "bind='" + bind + '\'' + + ", motd='" + motd + '\'' + + ", showMaxPlayers=" + showMaxPlayers + + ", onlineMode=" + onlineMode + + ", playerInfoForwardingMode=" + playerInfoForwardingMode + + ", servers=" + servers + + ", advanced=" + advanced + + ", query=" + query + + ", motdAsComponent=" + motdAsComponent + + ", favicon=" + favicon + + ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) + + '}'; } public static VelocityConfiguration read(Path path) throws IOException { - try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { - Toml toml = new Toml().read(reader); + Toml def = new Toml().read(VelocityServer.class.getResourceAsStream("/velocity.toml")); + Toml toml; + if (!path.toFile().exists()) { + toml = def; + } else { + try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + toml = new Toml(def).read(reader); + } + } - Map servers = new HashMap<>(); - for (Map.Entry entry : toml.getTable("servers").entrySet()) { - if (entry.getValue() instanceof String) { - servers.put(entry.getKey(), (String) entry.getValue()); - } else { - if (!entry.getKey().equalsIgnoreCase("try")) { - throw new IllegalArgumentException("Server entry " + entry.getKey() + " is not a string!"); - } + // TODO: Upgrdate old values to new, when config will be changed in future + Map servers = new HashMap<>(); + for (Map.Entry entry : toml.getTable("servers").entrySet()) { + if (entry.getValue() instanceof String) { + servers.put(entry.getKey(), (String) entry.getValue()); + } else { + if (!entry.getKey().equalsIgnoreCase("try")) { + throw new IllegalArgumentException("Server entry " + entry.getKey() + " is not a string!"); } } - - byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t") - .getBytes(StandardCharsets.UTF_8); - - return new VelocityConfiguration( - toml.getString("bind", "0.0.0.0:25577"), - toml.getString("motd", "&3A Velocity Server"), - toml.getLong("show-max-players", 500L).intValue(), - toml.getBoolean("online-mode", true), - PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()), - ImmutableMap.copyOf(servers), - toml.getTable("servers").getList("try"), - toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(), - toml.getTable("advanced").getLong("compression-level", -1L).intValue(), - toml.getTable("advanced").getLong("login-ratelimit", 3000L).intValue(), - toml.getTable("query").getBoolean("enabled", false), - toml.getTable("query").getLong("port", 25577L).intValue(), - forwardingSecret); } + + Servers serversTables = new Servers(ImmutableMap.copyOf(servers), toml.getTable("servers").getList("try")); + Advanced advanced = new Advanced(toml.getTable("advanced")); + Query query = new Query(toml.getTable("query")); + byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t") + .getBytes(StandardCharsets.UTF_8); + + return new VelocityConfiguration( + toml.getString("bind", "0.0.0.0:25577"), + toml.getString("motd", "&3A Velocity Server"), + toml.getLong("show-max-players", 500L).intValue(), + toml.getBoolean("online-mode", true), + PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()), + forwardingSecret, + serversTables, + advanced, + query + ); } + } From 64fadc436b46b79f8bb55ce63697d65032cc0c72 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Mon, 27 Aug 2018 19:25:36 +0300 Subject: [PATCH 2/6] Refactor VelocityConfiguration to better support for config upgrades --- .../velocitypowered/proxy/VelocityServer.java | 8 +- ...tationConfig.java => AnnotatedConfig.java} | 70 ++-- .../proxy/config/VelocityConfiguration.java | 307 ++++++++++++------ 3 files changed, 256 insertions(+), 129 deletions(-) rename proxy/src/main/java/com/velocitypowered/proxy/config/{AnnotationConfig.java => AnnotatedConfig.java} (71%) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 13903c3a8..17eb54960 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -21,7 +21,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.http.NettyHttpClient; import com.velocitypowered.proxy.command.VelocityCommandManager; -import com.velocitypowered.proxy.config.AnnotationConfig; +import com.velocitypowered.proxy.config.AnnotatedConfig; import com.velocitypowered.proxy.messages.VelocityChannelRegistrar; import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.protocol.util.FaviconSerializer; @@ -115,9 +115,9 @@ public class VelocityServer implements ProxyServer { System.exit(1); } - AnnotationConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values - - } catch (IOException | NullPointerException e) { + AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values + + } catch (IOException | RuntimeException e) { logger.error("Unable to load your velocity.toml. The server will shut down.", e); System.exit(1); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java similarity index 71% rename from proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java rename to proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index 6e83c2828..18707b1b7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotationConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -25,9 +25,9 @@ import org.apache.logging.log4j.Logger; /** * Only for simple configs */ -public class AnnotationConfig { +public class AnnotatedConfig { - private static final Logger logger = LogManager.getLogger(AnnotationConfig.class); + private static final Logger logger = LogManager.getLogger(AnnotatedConfig.class); public static Logger getLogger() { return logger; @@ -37,7 +37,7 @@ public class AnnotationConfig { * Indicates that a field is a table */ @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD}) + @Target({ElementType.FIELD, ElementType.TYPE}) public @interface Table { String value(); @@ -64,11 +64,12 @@ public class AnnotationConfig { } /** - * Indicates that a field is map and we need to save all data to config + * Indicates that a field is a map and we need to save all map data to + * config */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface AsMap { + public @interface IsMap { } /** @@ -76,28 +77,36 @@ public class AnnotationConfig { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface AsBytes { + public @interface StringAsBytes { } /** - * Indicates that a field is a string converted to byte[] + * Indicates that a filed should be skiped */ @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) + @Target({ElementType.FIELD}) public @interface Ignore { } public List dumpConfig() { List lines = new ArrayList<>(); - dumpFields(getClass(), this, lines); + if (!dumpFields(this, lines)) { + throw new RuntimeException("can not dump config"); + } return lines; } - private void dumpFields(Class root, Object caller, List lines) { + /** + * Dump all field and they annotations to List + * + * @param toSave object those we need to dump + * @param lines a list where store dumped lines + */ + private boolean dumpFields(Object toSave, List lines) { try { - for (Field field : root.getDeclaredFields()) { - if (field.getAnnotation(Ignore.class) != null) { + for (Field field : toSave.getClass().getDeclaredFields()) { + if (field.getAnnotation(Ignore.class) != null) { //Skip this field continue; } Comment comment = field.getAnnotation(Comment.class); @@ -106,34 +115,36 @@ public class AnnotationConfig { lines.add("# " + line); } } - CfgKey key = field.getAnnotation(CfgKey.class); - String name = key == null ? field.getName() : key.value(); - field.setAccessible(true); + CfgKey key = field.getAnnotation(CfgKey.class); //Get a key name for config + String name = key == null ? field.getName() : key.value(); // Use a field name if name in annotation is not present + field.setAccessible(true); // Make field accessible Table table = field.getAnnotation(Table.class); - if (table != null) { - lines.add(table.value()); // Write [name] - dumpFields(field.getType(), field.get(caller), lines); // dump a table class + if (table != null) { // Check if field is table. + lines.add(table.value()); // Write [name] + dumpFields(field.get(toSave), lines); // dump fields of table class } else { - if (field.getAnnotation(AsMap.class) != null) { - Map map = (Map) field.get(caller); + if (field.getAnnotation(IsMap.class) != null) { // check if field is map + Map map = (Map) field.get(toSave); for (Entry entry : map.entrySet()) { - lines.add(entry.getKey() + " = " + toString(entry.getValue())); + lines.add(entry.getKey() + " = " + toString(entry.getValue())); // Save a map data } - lines.add(""); + lines.add(""); //Add empty line continue; } - Object value = field.get(caller); - if (field.getAnnotation(AsBytes.class) != null) { + Object value = field.get(toSave); + if (field.getAnnotation(StringAsBytes.class) != null) { // Check if field is a byte[] representation of a string value = new String((byte[]) value, StandardCharsets.UTF_8); } - lines.add(name + " = " + toString(value)); - lines.add(""); + lines.add(name + " = " + toString(value)); // save field to config + lines.add(""); // add empty line } } } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { logger.log(Level.ERROR, "Unexpected error while dumping fields", e); lines.clear(); + return false; } + return true; } private String toString(Object value) { @@ -163,6 +174,13 @@ public class AnnotationConfig { return value != null ? value.toString() : "null"; } + /** + * Saves lines to file + * + * @param lines Lines to save + * @param to A path of file where to save lines + * @throws IOException if lines is empty or was error during saving + */ public static void saveConfig(List lines, Path to) throws IOException { if (lines.isEmpty()) { throw new IOException("Can not save config because list is empty"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 66a374d0b..058b9cb31 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -22,19 +22,23 @@ import java.util.List; import java.util.Map; import org.apache.logging.log4j.Logger; -public class VelocityConfiguration extends AnnotationConfig { +public class VelocityConfiguration extends AnnotatedConfig { + + @Comment("Config version. Do not change this") + @CfgKey("config-version") + private final String configVersion = "1.0"; @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") - private final String bind; + private String bind; @Comment("What should be the MOTD? Legacy color codes and JSON are accepted.") - private final String motd; + private String motd; @Comment({"What should we display for the maximum number of players? (Velocity does not support a cap", "on the number of players online.)"}) @CfgKey("show-max-players") - private final int showMaxPlayers; + private int showMaxPlayers; @Comment("Should we authenticate players with Mojang? By default, this is on.") @CfgKey("online-mode") - private final boolean onlineMode; + private boolean onlineMode; @Comment({"Should we forward IP addresses and other data to backend servers?", "Available options:", "- \"none\": No forwarding will be done. All players will appear to be Should we forward IP addresses and other data to backend servers?connecting from the proxy", @@ -44,94 +48,21 @@ public class VelocityConfiguration extends AnnotationConfig { "- \"modern\": Forward player IPs and UUIDs as part of the login process using Velocity's native", " forwarding. Only applicable for Minecraft 1.13 or higher."}) @CfgKey("player-info-forwarding-mode") - private final PlayerInfoForwarding playerInfoForwardingMode; + private PlayerInfoForwarding playerInfoForwardingMode; - @AsBytes + @StringAsBytes @Comment("If you are using modern IP forwarding, configure an unique secret here.") @CfgKey("forwarding-secret") - private final byte[] forwardingSecret; - + private byte[] forwardingSecret; @Table("[servers]") private final Servers servers; - private static class Servers { - - @AsMap - @Comment("Configure your servers here.") - public final Map servers; - - @Comment("In what order we should try servers when a player logs in or is kicked from a server.") - @CfgKey("try") - public final List attemptConnectionOrder; - - public Servers(Map servers, List attemptConnectionOrder) { - this.servers = servers; - this.attemptConnectionOrder = attemptConnectionOrder; - } - - @Override - public String toString() { - return "Servers{" + "servers=" + servers + ", attemptConnectionOrder=" + attemptConnectionOrder + '}'; - } - - } - @Table("[advanced]") private final Advanced advanced; - private static class Advanced { - - @Comment({"How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", - "setting it to -1 will disable compression entirely."}) - @CfgKey("compression-threshold") - public final int compressionThreshold; - @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") - @CfgKey("compression-level") - public final int compressionLevel; - @Comment({"How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", - "Disable by setting to 0"}) - @CfgKey("login-ratelimit") - public final int loginRatelimit; - - public Advanced(Toml toml) { - this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); - this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); - this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); - } - - @Override - public String toString() { - return "Advanced{" + "compressionThreshold=" + compressionThreshold + ", compressionLevel=" + compressionLevel + ", loginRatelimit=" + loginRatelimit + '}'; - } - } - @Table("[query]") private final Query query; - private static class Query { - - @Comment("Whether to enable responding to GameSpy 4 query responses or not") - @CfgKey("enabled") - public final boolean queryEnabled; - @Comment("If query responding is enabled, on what port should query response listener listen on?") - @CfgKey("port") - public final int queryPort; - - public Query(boolean queryEnabled, int queryPort) { - this.queryEnabled = queryEnabled; - this.queryPort = queryPort; - } - - private Query(Toml toml) { - this.queryEnabled = toml.getBoolean("enabled", false); - this.queryPort = toml.getLong("port", 25577L).intValue(); - } - - @Override - public String toString() { - return "Query{" + "queryEnabled=" + queryEnabled + ", queryPort=" + queryPort + '}'; - } - } @Ignore private Component motdAsComponent; @Ignore @@ -153,7 +84,7 @@ public class VelocityConfiguration extends AnnotationConfig { public boolean validate() { boolean valid = true; - Logger logger = AnnotationConfig.getLogger(); + Logger logger = AnnotatedConfig.getLogger(); if (bind.isEmpty()) { logger.error("'bind' option is empty."); @@ -183,16 +114,16 @@ public class VelocityConfiguration extends AnnotationConfig { break; } - if (servers.servers.isEmpty()) { + if (servers.getServers().isEmpty()) { logger.error("You have no servers configured. :("); valid = false; } else { - if (servers.attemptConnectionOrder.isEmpty()) { + if (servers.getAttemptConnectionOrder().isEmpty()) { logger.error("No fallback servers are configured!"); valid = false; } - for (Map.Entry entry : servers.servers.entrySet()) { + for (Map.Entry entry : servers.getServers().entrySet()) { try { AddressUtil.parseAddress(entry.getValue()); } catch (IllegalArgumentException e) { @@ -201,8 +132,8 @@ public class VelocityConfiguration extends AnnotationConfig { } } - for (String s : servers.attemptConnectionOrder) { - if (!servers.servers.containsKey(s)) { + for (String s : servers.getAttemptConnectionOrder()) { + if (!servers.getServers().containsKey(s)) { logger.error("Fallback server " + s + " doesn't exist!"); valid = false; } @@ -256,11 +187,11 @@ public class VelocityConfiguration extends AnnotationConfig { } public boolean isQueryEnabled() { - return query.queryEnabled; + return query.isQueryEnabled(); } public int getQueryPort() { - return query.queryPort; + return query.getQueryPort(); } public String getMotd() { @@ -290,32 +221,64 @@ public class VelocityConfiguration extends AnnotationConfig { return playerInfoForwardingMode; } + public byte[] getForwardingSecret() { + return forwardingSecret; + } + public Map getServers() { - return servers.servers; + return servers.getServers(); } public List getAttemptConnectionOrder() { - return servers.attemptConnectionOrder; + return servers.getAttemptConnectionOrder(); } public int getCompressionThreshold() { - return advanced.compressionThreshold; + return advanced.getCompressionThreshold(); } public int getCompressionLevel() { - return advanced.compressionLevel; + return advanced.getCompressionLevel(); } public int getLoginRatelimit() { - return advanced.loginRatelimit; + return advanced.getLoginRatelimit(); } public Favicon getFavicon() { return favicon; } - public byte[] getForwardingSecret() { - return forwardingSecret; + private void setBind(String bind) { + this.bind = bind; + } + + private void setMotd(String motd) { + this.motd = motd; + } + + private void setShowMaxPlayers(int showMaxPlayers) { + this.showMaxPlayers = showMaxPlayers; + } + + private void setOnlineMode(boolean onlineMode) { + this.onlineMode = onlineMode; + } + + private void setPlayerInfoForwardingMode(PlayerInfoForwarding playerInfoForwardingMode) { + this.playerInfoForwardingMode = playerInfoForwardingMode; + } + + private void setForwardingSecret(byte[] forwardingSecret) { + this.forwardingSecret = forwardingSecret; + } + + private void setMotdAsComponent(Component motdAsComponent) { + this.motdAsComponent = motdAsComponent; + } + + private void setFavicon(Favicon favicon) { + this.favicon = favicon; } @Override @@ -327,12 +290,12 @@ public class VelocityConfiguration extends AnnotationConfig { + ", showMaxPlayers=" + showMaxPlayers + ", onlineMode=" + onlineMode + ", playerInfoForwardingMode=" + playerInfoForwardingMode + + ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) + ", servers=" + servers + ", advanced=" + advanced + ", query=" + query + ", motdAsComponent=" + motdAsComponent + ", favicon=" + favicon - + ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) + '}'; } @@ -347,7 +310,7 @@ public class VelocityConfiguration extends AnnotationConfig { } } - // TODO: Upgrdate old values to new, when config will be changed in future + // If config will be changed in future, do not forget to migrate old values if needed Map servers = new HashMap<>(); for (Map.Entry entry : toml.getTable("servers").entrySet()) { if (entry.getValue() instanceof String) { @@ -365,7 +328,7 @@ public class VelocityConfiguration extends AnnotationConfig { byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t") .getBytes(StandardCharsets.UTF_8); - return new VelocityConfiguration( + VelocityConfiguration configuration = new VelocityConfiguration( toml.getString("bind", "0.0.0.0:25577"), toml.getString("motd", "&3A Velocity Server"), toml.getLong("show-max-players", 500L).intValue(), @@ -376,6 +339,152 @@ public class VelocityConfiguration extends AnnotationConfig { advanced, query ); + upgradeConfig(configuration, toml); + return configuration; } + private static void upgradeConfig(VelocityConfiguration configuration, Toml toml) { + switch (toml.getString("config-version", configuration.configVersion)) { + case "1.0": + //TODO: Upgrade a 1.0 config to a new version. Maybe add a recursive support in future. + break; + default: + break; + } + } + + private static class Servers { + + @IsMap + @Comment("Configure your servers here.") + private Map servers; + + @Comment("In what order we should try servers when a player logs in or is kicked from a server.") + @CfgKey("try") + private List attemptConnectionOrder; + + private Servers(Map servers, List attemptConnectionOrder) { + this.servers = servers; + this.attemptConnectionOrder = attemptConnectionOrder; + } + + private Map getServers() { + return servers; + } + + public void setServers(Map servers) { + this.servers = servers; + } + + public List getAttemptConnectionOrder() { + return attemptConnectionOrder; + } + + public void setAttemptConnectionOrder(List attemptConnectionOrder) { + this.attemptConnectionOrder = attemptConnectionOrder; + } + + @Override + public String toString() { + return "Servers{" + "servers=" + servers + ", attemptConnectionOrder=" + attemptConnectionOrder + '}'; + } + + } + + private static class Advanced { + + @Comment({"How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", + "setting it to -1 will disable compression entirely."}) + @CfgKey("compression-threshold") + private int compressionThreshold; + @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") + @CfgKey("compression-level") + private int compressionLevel; + @Comment({"How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", + "Disable by setting to 0"}) + @CfgKey("login-ratelimit") + private int loginRatelimit; + + private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) { + this.compressionThreshold = compressionThreshold; + this.compressionLevel = compressionLevel; + this.loginRatelimit = loginRatelimit; + } + + private Advanced(Toml toml) { + this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); + this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); + this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); + } + + public int getCompressionThreshold() { + return compressionThreshold; + } + + public void setCompressionThreshold(int compressionThreshold) { + this.compressionThreshold = compressionThreshold; + } + + public int getCompressionLevel() { + return compressionLevel; + } + + public void setCompressionLevel(int compressionLevel) { + this.compressionLevel = compressionLevel; + } + + public int getLoginRatelimit() { + return loginRatelimit; + } + + public void setLoginRatelimit(int loginRatelimit) { + this.loginRatelimit = loginRatelimit; + } + + @Override + public String toString() { + return "Advanced{" + "compressionThreshold=" + compressionThreshold + ", compressionLevel=" + compressionLevel + ", loginRatelimit=" + loginRatelimit + '}'; + } + } + + private static class Query { + + @Comment("Whether to enable responding to GameSpy 4 query responses or not") + @CfgKey("enabled") + private boolean queryEnabled; + @Comment("If query responding is enabled, on what port should query response listener listen on?") + @CfgKey("port") + private int queryPort; + + private Query(boolean queryEnabled, int queryPort) { + this.queryEnabled = queryEnabled; + this.queryPort = queryPort; + } + + private Query(Toml toml) { + this.queryEnabled = toml.getBoolean("enabled", false); + this.queryPort = toml.getLong("port", 25577L).intValue(); + } + + public boolean isQueryEnabled() { + return queryEnabled; + } + + public void setQueryEnabled(boolean queryEnabled) { + this.queryEnabled = queryEnabled; + } + + public int getQueryPort() { + return queryPort; + } + + public void setQueryPort(int queryPort) { + this.queryPort = queryPort; + } + + @Override + public String toString() { + return "Query{" + "queryEnabled=" + queryEnabled + ", queryPort=" + queryPort + '}'; + } + } } From 7e215e3c4f56deae6d15b8dcd27563b7f7003542 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Tue, 28 Aug 2018 15:52:32 +0300 Subject: [PATCH 3/6] CfgKey -> ConfigKey --- .../proxy/config/AnnotatedConfig.java | 4 ++-- .../proxy/config/VelocityConfiguration.java | 22 +++++++++---------- proxy/src/main/resources/velocity.toml | 3 +++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index 18707b1b7..d46205616 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -58,7 +58,7 @@ public class AnnotatedConfig { */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface CfgKey { + public @interface ConfigKey { String value(); } @@ -115,7 +115,7 @@ public class AnnotatedConfig { lines.add("# " + line); } } - CfgKey key = field.getAnnotation(CfgKey.class); //Get a key name for config + ConfigKey key = field.getAnnotation(ConfigKey.class); //Get a key name for config String name = key == null ? field.getName() : key.value(); // Use a field name if name in annotation is not present field.setAccessible(true); // Make field accessible Table table = field.getAnnotation(Table.class); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 058b9cb31..811e35ded 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -25,7 +25,7 @@ import org.apache.logging.log4j.Logger; public class VelocityConfiguration extends AnnotatedConfig { @Comment("Config version. Do not change this") - @CfgKey("config-version") + @ConfigKey("config-version") private final String configVersion = "1.0"; @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") @@ -34,10 +34,10 @@ public class VelocityConfiguration extends AnnotatedConfig { private String motd; @Comment({"What should we display for the maximum number of players? (Velocity does not support a cap", "on the number of players online.)"}) - @CfgKey("show-max-players") + @ConfigKey("show-max-players") private int showMaxPlayers; @Comment("Should we authenticate players with Mojang? By default, this is on.") - @CfgKey("online-mode") + @ConfigKey("online-mode") private boolean onlineMode; @Comment({"Should we forward IP addresses and other data to backend servers?", "Available options:", @@ -47,12 +47,12 @@ public class VelocityConfiguration extends AnnotatedConfig { " servers using Minecraft 1.12 or lower.", "- \"modern\": Forward player IPs and UUIDs as part of the login process using Velocity's native", " forwarding. Only applicable for Minecraft 1.13 or higher."}) - @CfgKey("player-info-forwarding-mode") + @ConfigKey("player-info-forwarding-mode") private PlayerInfoForwarding playerInfoForwardingMode; @StringAsBytes @Comment("If you are using modern IP forwarding, configure an unique secret here.") - @CfgKey("forwarding-secret") + @ConfigKey("forwarding-secret") private byte[] forwardingSecret; @Table("[servers]") private final Servers servers; @@ -360,7 +360,7 @@ public class VelocityConfiguration extends AnnotatedConfig { private Map servers; @Comment("In what order we should try servers when a player logs in or is kicked from a server.") - @CfgKey("try") + @ConfigKey("try") private List attemptConnectionOrder; private Servers(Map servers, List attemptConnectionOrder) { @@ -395,14 +395,14 @@ public class VelocityConfiguration extends AnnotatedConfig { @Comment({"How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", "setting it to -1 will disable compression entirely."}) - @CfgKey("compression-threshold") + @ConfigKey("compression-threshold") private int compressionThreshold; @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") - @CfgKey("compression-level") + @ConfigKey("compression-level") private int compressionLevel; @Comment({"How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", "Disable by setting to 0"}) - @CfgKey("login-ratelimit") + @ConfigKey("login-ratelimit") private int loginRatelimit; private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) { @@ -450,10 +450,10 @@ public class VelocityConfiguration extends AnnotatedConfig { private static class Query { @Comment("Whether to enable responding to GameSpy 4 query responses or not") - @CfgKey("enabled") + @ConfigKey("enabled") private boolean queryEnabled; @Comment("If query responding is enabled, on what port should query response listener listen on?") - @CfgKey("port") + @ConfigKey("port") private int queryPort; private Query(boolean queryEnabled, int queryPort) { diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml index 24a7921bd..8a35577fb 100644 --- a/proxy/src/main/resources/velocity.toml +++ b/proxy/src/main/resources/velocity.toml @@ -1,3 +1,6 @@ +# Config version. Do not change this +config-version = "1.0" + # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577. bind = "0.0.0.0:25577" From a9c4d1d88bfc2675f6fd50c234d131a83c0d3320 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Wed, 29 Aug 2018 15:54:13 +0300 Subject: [PATCH 4/6] Make the velocity generate a default config --- .../velocitypowered/proxy/VelocityServer.java | 2 +- .../proxy/config/AnnotatedConfig.java | 2 +- .../proxy/config/VelocityConfiguration.java | 97 ++++++++++++------- proxy/src/main/resources/velocity.toml | 58 ----------- 4 files changed, 64 insertions(+), 95 deletions(-) delete mode 100644 proxy/src/main/resources/velocity.toml diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 17eb54960..6c4ffca86 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -116,7 +116,7 @@ public class VelocityServer implements ProxyServer { } AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values - + } catch (IOException | RuntimeException e) { logger.error("Unable to load your velocity.toml. The server will shut down.", e); System.exit(1); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index d46205616..329ddd5d4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -81,7 +81,7 @@ public class AnnotatedConfig { } /** - * Indicates that a filed should be skiped + * Indicates that a field should be skiped */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 811e35ded..e3008e746 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -17,9 +17,11 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; import org.apache.logging.log4j.Logger; public class VelocityConfiguration extends AnnotatedConfig { @@ -29,16 +31,16 @@ public class VelocityConfiguration extends AnnotatedConfig { private final String configVersion = "1.0"; @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") - private String bind; + private String bind = "0.0.0.0:25577"; @Comment("What should be the MOTD? Legacy color codes and JSON are accepted.") - private String motd; + private String motd = "&3A Velocity Server"; @Comment({"What should we display for the maximum number of players? (Velocity does not support a cap", "on the number of players online.)"}) @ConfigKey("show-max-players") - private int showMaxPlayers; + private int showMaxPlayers = 500; @Comment("Should we authenticate players with Mojang? By default, this is on.") @ConfigKey("online-mode") - private boolean onlineMode; + private boolean onlineMode = true; @Comment({"Should we forward IP addresses and other data to backend servers?", "Available options:", "- \"none\": No forwarding will be done. All players will appear to be Should we forward IP addresses and other data to backend servers?connecting from the proxy", @@ -48,12 +50,13 @@ public class VelocityConfiguration extends AnnotatedConfig { "- \"modern\": Forward player IPs and UUIDs as part of the login process using Velocity's native", " forwarding. Only applicable for Minecraft 1.13 or higher."}) @ConfigKey("player-info-forwarding-mode") - private PlayerInfoForwarding playerInfoForwardingMode; + private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.MODERN; @StringAsBytes @Comment("If you are using modern IP forwarding, configure an unique secret here.") @ConfigKey("forwarding-secret") - private byte[] forwardingSecret; + private byte[] forwardingSecret = new Random().ints(48, 123).filter(i -> (i < 58) || (i > 64 && i < 91) || (i > 96)).limit(12) + .collect(StringBuilder::new, (sb, i) -> sb.append((char) i), StringBuilder::append).toString().getBytes(StandardCharsets.UTF_8); //One line string generation @Table("[servers]") private final Servers servers; @@ -68,6 +71,12 @@ public class VelocityConfiguration extends AnnotatedConfig { @Ignore private Favicon favicon; + public VelocityConfiguration(Servers servers, Advanced advanced, Query query) { + this.servers = servers; + this.advanced = advanced; + this.query = query; + } + private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, Servers servers, Advanced advanced, Query query) { @@ -300,29 +309,17 @@ public class VelocityConfiguration extends AnnotatedConfig { } public static VelocityConfiguration read(Path path) throws IOException { - Toml def = new Toml().read(VelocityServer.class.getResourceAsStream("/velocity.toml")); Toml toml; if (!path.toFile().exists()) { - toml = def; + getLogger().info("No velocity.toml found, creating one for you..."); + return new VelocityConfiguration(new Servers(), new Advanced(), new Query()); } else { try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { - toml = new Toml(def).read(reader); + toml = new Toml().read(reader); } } - // If config will be changed in future, do not forget to migrate old values if needed - Map servers = new HashMap<>(); - for (Map.Entry entry : toml.getTable("servers").entrySet()) { - if (entry.getValue() instanceof String) { - servers.put(entry.getKey(), (String) entry.getValue()); - } else { - if (!entry.getKey().equalsIgnoreCase("try")) { - throw new IllegalArgumentException("Server entry " + entry.getKey() + " is not a string!"); - } - } - } - - Servers serversTables = new Servers(ImmutableMap.copyOf(servers), toml.getTable("servers").getList("try")); + Servers servers = new Servers(toml.getTable("servers")); Advanced advanced = new Advanced(toml.getTable("advanced")); Query query = new Query(toml.getTable("query")); byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t") @@ -335,7 +332,7 @@ public class VelocityConfiguration extends AnnotatedConfig { toml.getBoolean("online-mode", true), PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()), forwardingSecret, - serversTables, + servers, advanced, query ); @@ -357,11 +354,31 @@ public class VelocityConfiguration extends AnnotatedConfig { @IsMap @Comment("Configure your servers here.") - private Map servers; + private Map servers = ImmutableMap.of("lobby", "127.0.0.1:30066", "factions", "127.0.0.1:30067", "minigames", "127.0.0.1:30068"); @Comment("In what order we should try servers when a player logs in or is kicked from a server.") @ConfigKey("try") - private List attemptConnectionOrder; + private List attemptConnectionOrder = Arrays.asList("lobby"); + + private Servers() { + } + + private Servers(Toml toml) { + if (toml != null) { + Map servers = new HashMap<>(); + for (Map.Entry entry : toml.entrySet()) { + if (entry.getValue() instanceof String) { + servers.put(entry.getKey(), (String) entry.getValue()); + } else { + if (!entry.getKey().equalsIgnoreCase("try")) { + throw new IllegalArgumentException("Server entry " + entry.getKey() + " is not a string!"); + } + } + } + this.servers = ImmutableMap.copyOf(servers); + this.attemptConnectionOrder = toml.getList("try", attemptConnectionOrder); + } + } private Servers(Map servers, List attemptConnectionOrder) { this.servers = servers; @@ -396,14 +413,17 @@ public class VelocityConfiguration extends AnnotatedConfig { @Comment({"How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", "setting it to -1 will disable compression entirely."}) @ConfigKey("compression-threshold") - private int compressionThreshold; + private int compressionThreshold = 1024; @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") @ConfigKey("compression-level") - private int compressionLevel; + private int compressionLevel = -1; @Comment({"How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", "Disable by setting to 0"}) @ConfigKey("login-ratelimit") - private int loginRatelimit; + private int loginRatelimit = 3000; + + private Advanced() { + } private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) { this.compressionThreshold = compressionThreshold; @@ -412,9 +432,11 @@ public class VelocityConfiguration extends AnnotatedConfig { } private Advanced(Toml toml) { - this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); - this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); - this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); + if (toml != null) { + this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue(); + this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); + this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); + } } public int getCompressionThreshold() { @@ -451,10 +473,13 @@ public class VelocityConfiguration extends AnnotatedConfig { @Comment("Whether to enable responding to GameSpy 4 query responses or not") @ConfigKey("enabled") - private boolean queryEnabled; + private boolean queryEnabled = false; @Comment("If query responding is enabled, on what port should query response listener listen on?") @ConfigKey("port") - private int queryPort; + private int queryPort = 25577; + + private Query() { + } private Query(boolean queryEnabled, int queryPort) { this.queryEnabled = queryEnabled; @@ -462,8 +487,10 @@ public class VelocityConfiguration extends AnnotatedConfig { } private Query(Toml toml) { - this.queryEnabled = toml.getBoolean("enabled", false); - this.queryPort = toml.getLong("port", 25577L).intValue(); + if (toml != null) { + this.queryEnabled = toml.getBoolean("enabled", false); + this.queryPort = toml.getLong("port", 25577L).intValue(); + } } public boolean isQueryEnabled() { diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml deleted file mode 100644 index 8a35577fb..000000000 --- a/proxy/src/main/resources/velocity.toml +++ /dev/null @@ -1,58 +0,0 @@ -# Config version. Do not change this -config-version = "1.0" - -# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577. -bind = "0.0.0.0:25577" - -# What should be the MOTD? Legacy color codes and JSON are accepted. -motd = "&3A Velocity Server" - -# What should we display for the maximum number of players? (Velocity does not support a cap -# on the number of players online.) -show-max-players = 500 - -# Should we authenticate players with Mojang? By default, this is on. -online-mode = true - -# Should we forward IP addresses and other data to backend servers? -# Available options: -# - "none": No forwarding will be done. All players will appear to be connecting from the proxy -# and will have offline-mode UUIDs. -# - "legacy": Forward player IPs and UUIDs in BungeeCord-compatible fashion. Use this if you run -# servers using Minecraft 1.12 or lower. -# - "modern": Forward player IPs and UUIDs as part of the login process using Velocity's native -# forwarding. Only applicable for Minecraft 1.13 or higher. -player-info-forwarding = "modern" - -# If you are using modern IP forwarding, configure an unique secret here. -player-info-forwarding-secret = "5up3r53cr3t" - -[servers] -# Configure your servers here. -lobby = "127.0.0.1:30066" -factions = "127.0.0.1:30067" -minigames = "127.0.0.1:30068" - -# In what order we should try servers when a player logs in or is kicked from a server. -try = [ - "lobby" -] - -[advanced] -# How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and -# setting it to -1 will disable compression entirely. -compression-threshold = 1024 - -# How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6. -compression-level = -1 - -# How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000 -# Disable by setting to 0 -login-ratelimit = 3000 - -[query] -# Whether to enable responding to GameSpy 4 query responses or not -enabled = false - -# If query responding is enabled, on what port should query response listener listen on? -port = 25577 \ No newline at end of file From b201d82a312b044bddbc56d387acdc855445b525 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Fri, 31 Aug 2018 16:50:14 +0300 Subject: [PATCH 5/6] cleanup --- .../velocitypowered/proxy/VelocityServer.java | 4 +-- .../proxy/config/AnnotatedConfig.java | 12 ++----- .../proxy/config/VelocityConfiguration.java | 33 +++++++++++++++---- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index b4345a354..03ecfc710 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -118,8 +118,8 @@ public class VelocityServer implements ProxyServer { AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values - } catch (IOException | RuntimeException e) { - logger.error("Unable to load your velocity.toml. The server will shut down.", e); + } catch (Throwable e) { + logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e); LogManager.shutdown(); System.exit(1); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index 329ddd5d4..05952bb02 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -18,7 +18,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -90,9 +89,7 @@ public class AnnotatedConfig { public List dumpConfig() { List lines = new ArrayList<>(); - if (!dumpFields(this, lines)) { - throw new RuntimeException("can not dump config"); - } + dumpFields(this, lines); return lines; } @@ -102,7 +99,7 @@ public class AnnotatedConfig { * @param toSave object those we need to dump * @param lines a list where store dumped lines */ - private boolean dumpFields(Object toSave, List lines) { + private void dumpFields(Object toSave, List lines) { try { for (Field field : toSave.getClass().getDeclaredFields()) { @@ -140,11 +137,8 @@ public class AnnotatedConfig { } } } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { - logger.log(Level.ERROR, "Unexpected error while dumping fields", e); - lines.clear(); - return false; + throw new RuntimeException("Can not dump config", e); } - return true; } private String toString(Object value) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index e3008e746..b55e82480 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -32,16 +32,22 @@ public class VelocityConfiguration extends AnnotatedConfig { @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577.") private String bind = "0.0.0.0:25577"; + @Comment("What should be the MOTD? Legacy color codes and JSON are accepted.") private String motd = "&3A Velocity Server"; - @Comment({"What should we display for the maximum number of players? (Velocity does not support a cap", + + @Comment({ + "What should we display for the maximum number of players? (Velocity does not support a cap", "on the number of players online.)"}) @ConfigKey("show-max-players") private int showMaxPlayers = 500; + @Comment("Should we authenticate players with Mojang? By default, this is on.") @ConfigKey("online-mode") private boolean onlineMode = true; - @Comment({"Should we forward IP addresses and other data to backend servers?", + + @Comment({ + "Should we forward IP addresses and other data to backend servers?", "Available options:", "- \"none\": No forwarding will be done. All players will appear to be Should we forward IP addresses and other data to backend servers?connecting from the proxy", " and will have offline-mode UUIDs.", @@ -57,6 +63,7 @@ public class VelocityConfiguration extends AnnotatedConfig { @ConfigKey("forwarding-secret") private byte[] forwardingSecret = new Random().ints(48, 123).filter(i -> (i < 58) || (i > 64 && i < 91) || (i > 96)).limit(12) .collect(StringBuilder::new, (sb, i) -> sb.append((char) i), StringBuilder::append).toString().getBytes(StandardCharsets.UTF_8); //One line string generation + @Table("[servers]") private final Servers servers; @@ -403,21 +410,26 @@ public class VelocityConfiguration extends AnnotatedConfig { @Override public String toString() { - return "Servers{" + "servers=" + servers + ", attemptConnectionOrder=" + attemptConnectionOrder + '}'; + return "Servers{" + + "servers=" + servers + + ", attemptConnectionOrder=" + attemptConnectionOrder + + '}'; } } private static class Advanced { - @Comment({"How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", + @Comment({ + "How large a Minecraft packet has to be before we compress it. Setting this to zero will compress all packets, and", "setting it to -1 will disable compression entirely."}) @ConfigKey("compression-threshold") private int compressionThreshold = 1024; @Comment("How much compression should be done (from 0-9). The default is -1, which uses zlib's default level of 6.") @ConfigKey("compression-level") private int compressionLevel = -1; - @Comment({"How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", + @Comment({ + "How fast (in miliseconds) are clients allowed to connect after the last connection? Default: 3000", "Disable by setting to 0"}) @ConfigKey("login-ratelimit") private int loginRatelimit = 3000; @@ -465,7 +477,11 @@ public class VelocityConfiguration extends AnnotatedConfig { @Override public String toString() { - return "Advanced{" + "compressionThreshold=" + compressionThreshold + ", compressionLevel=" + compressionLevel + ", loginRatelimit=" + loginRatelimit + '}'; + return "Advanced{" + + "compressionThreshold=" + compressionThreshold + + ", compressionLevel=" + compressionLevel + + ", loginRatelimit=" + loginRatelimit + + '}'; } } @@ -511,7 +527,10 @@ public class VelocityConfiguration extends AnnotatedConfig { @Override public String toString() { - return "Query{" + "queryEnabled=" + queryEnabled + ", queryPort=" + queryPort + '}'; + return "Query{" + + "queryEnabled=" + queryEnabled + + ", queryPort=" + queryPort + + '}'; } } } From 57ccb6eec2b683802a9a49420029f06cefd09365 Mon Sep 17 00:00:00 2001 From: Leymooo Date: Fri, 31 Aug 2018 20:36:10 +0300 Subject: [PATCH 6/6] Allow using a '\n' in config. Create a method to generate a random string. --- .../proxy/config/AnnotatedConfig.java | 2 +- .../proxy/config/VelocityConfiguration.java | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java index 05952bb02..32f9a4880 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java @@ -163,7 +163,7 @@ public class AnnotatedConfig { if (stringValue.isEmpty()) { return "\"\""; } - return "\"" + stringValue + "\""; + return "\"" + stringValue.replace("\n", "\\n") + "\""; } return value != null ? value.toString() : "null"; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index b55e82480..cf223fe60 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -5,7 +5,6 @@ import com.moandjiezana.toml.Toml; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.api.util.LegacyChatColorUtils; -import com.velocitypowered.proxy.VelocityServer; import io.netty.buffer.ByteBufUtil; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; @@ -61,8 +60,7 @@ public class VelocityConfiguration extends AnnotatedConfig { @StringAsBytes @Comment("If you are using modern IP forwarding, configure an unique secret here.") @ConfigKey("forwarding-secret") - private byte[] forwardingSecret = new Random().ints(48, 123).filter(i -> (i < 58) || (i > 64 && i < 91) || (i > 96)).limit(12) - .collect(StringBuilder::new, (sb, i) -> sb.append((char) i), StringBuilder::append).toString().getBytes(StandardCharsets.UTF_8); //One line string generation + private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); @Table("[servers]") private final Servers servers; @@ -357,6 +355,16 @@ public class VelocityConfiguration extends AnnotatedConfig { } } + private static String generateRandomString(int lenght) { + String chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890"; + StringBuilder builder = new StringBuilder(); + Random rnd = new Random(); + for (int i = 0; i < lenght; i++) { + builder.append(chars.charAt(rnd.nextInt(chars.length()))); + } + return builder.toString(); + } + private static class Servers { @IsMap