diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 123a9a60..f58a997e 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -25,6 +25,11 @@ package org.geysermc.platform.standalone; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import lombok.Getter; import net.minecrell.terminalconsole.TerminalConsoleAppender; import org.apache.logging.log4j.LogManager; @@ -36,6 +41,7 @@ import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.common.PlatformType; import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.configuration.GeyserJacksonConfiguration; import org.geysermc.connector.dump.BootstrapDumpInfo; import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; @@ -50,7 +56,8 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; -import java.util.UUID; +import java.util.*; +import java.util.stream.Collectors; public class GeyserStandaloneBootstrap implements GeyserBootstrap { @@ -67,6 +74,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { private GeyserConnector connector; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final Map argsConfigKeys = new HashMap<>(); public static void main(String[] args) { GeyserStandaloneBootstrap bootstrap = new GeyserStandaloneBootstrap(); @@ -74,6 +84,8 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { boolean useGuiOpts = bootstrap.useGui; String configFilenameOpt = bootstrap.configFilename; + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + for (int i = 0; i < args.length; i++) { // By default, standalone Geyser will check if it should open the GUI based on if the GUI is null // Optionally, you can force the use of a GUI or no GUI by specifying args @@ -106,8 +118,43 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { System.out.println(" --gui, --nogui " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.gui")); return; default: - String badArgMsg = LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised"); - System.err.println(MessageFormat.format(badArgMsg, arg)); + // We have likely added a config option argument + if (arg.startsWith("--")) { + // Split the argument by an = + String[] argParts = arg.substring(2).split("="); + if (argParts.length == 2) { + // Split the config key by . to allow for nested options + String[] configKeyParts = argParts[0].split("\\."); + + // Loop the possible config options to check the passed key is valid + boolean found = false; + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop sub-section options to check the passed key is valid + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + found = true; + break; + } + } + } else { + found = true; + } + + break; + } + } + + // Add the found key to the stored list for later usage + if (found) { + argsConfigKeys.put(argParts[0], argParts[1]); + break; + } + } + } + + System.err.println(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.unrecognised", arg)); return; } } @@ -148,6 +195,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { try { File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml", (x) -> x.replaceAll("generateduuid", UUID.randomUUID().toString())); geyserConfig = FileUtils.loadConfig(configFile, GeyserStandaloneConfiguration.class); + + handleArgsConfigOptions(); + if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); // Doesn't really need to be set but /shrug geyserConfig.getRemote().setAddress("127.0.0.1"); @@ -223,4 +273,99 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserStandaloneDumpInfo(this); } + + /** + * Get the {@link BeanPropertyDefinition}s for the given class + * + * @param clazz The class to get the definitions for + * @return A list of {@link BeanPropertyDefinition} for the given class + */ + public static List getPOJOForClass(Class clazz) { + JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructType(clazz); + + // Introspect the given type + BeanDescription beanDescription = OBJECT_MAPPER.getSerializationConfig().introspect(javaType); + + // Find properties + List properties = beanDescription.findProperties(); + + // Get the ignored properties + Set ignoredProperties = OBJECT_MAPPER.getSerializationConfig().getAnnotationIntrospector() + .findPropertyIgnorals(beanDescription.getClassInfo()).getIgnored(); + + // Filter properties removing the ignored ones + return properties.stream() + .filter(property -> !ignoredProperties.contains(property.getName())) + .collect(Collectors.toList()); + } + + /** + * Set a POJO property value on an object + * + * @param property The {@link BeanPropertyDefinition} to set + * @param parentObject The object to alter + * @param value The new value of the property + */ + private static void setConfigOption(BeanPropertyDefinition property, Object parentObject, Object value) { + Object parsedValue = value; + + // Change the values type if needed + if (int.class.equals(property.getRawPrimaryType())) { + parsedValue = Integer.valueOf((String) parsedValue); + } else if (boolean.class.equals(property.getRawPrimaryType())) { + parsedValue = Boolean.valueOf((String) parsedValue); + } + + // Force the value to be set + AnnotatedField field = property.getField(); + field.fixAccess(true); + field.setValue(parentObject, parsedValue); + } + + /** + * Update the loaded {@link GeyserStandaloneConfiguration} with any values passed in the command line arguments + */ + private void handleArgsConfigOptions() { + // Get the available properties from the class + List availableProperties = getPOJOForClass(GeyserJacksonConfiguration.class); + + for (Map.Entry configKey : argsConfigKeys.entrySet()) { + String[] configKeyParts = configKey.getKey().split("\\."); + + // Loop over the properties looking for any matches against the stored one from the argument + for (BeanPropertyDefinition property : availableProperties) { + if (configKeyParts[0].equals(property.getName())) { + if (configKeyParts.length > 1) { + // Loop through the sub property if the first part matches + for (BeanPropertyDefinition subProperty : getPOJOForClass(property.getRawPrimaryType())) { + if (configKeyParts[1].equals(subProperty.getName())) { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the sub property value on the config + try { + Object subConfig = property.getGetter().callOn(geyserConfig); + setConfigOption(subProperty, subConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + + break; + } + } + } else { + geyserLogger.info(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.set_config_option", configKey.getKey(), configKey.getValue())); + + // Set the property value on the config + try { + setConfigOption(property, geyserConfig, configKey.getValue()); + } catch (Exception e) { + geyserLogger.error("Failed to set config option: " + property.getFullName()); + } + } + + break; + } + } + } + } } diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index b7ef31bd..bf4b0b71 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit b7ef31bd9c45aa3a0735883764c231f30cb55bfa +Subproject commit bf4b0b7103193154dd0b06e0459dc375c753069a