diff --git a/build.gradle b/build.gradle
index 8410bf59..2597897a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,7 +63,6 @@ dependencies {
includeInJar "com.formdev:flatlaf-extras:3.7.1"
includeInJar "org.apache.commons:commons-lang3:3.20.0"
includeInJar "commons-io:commons-io:2.22.0"
- includeInJar "net.sf.jopt-simple:jopt-simple:5.0.4"
includeInJar "org.apache.logging.log4j:log4j-core:2.25.4"
includeInJar "org.apache.logging.log4j:log4j-slf4j2-impl:2.25.4"
includeInJar "org.jline:jansi:4.0.14"
@@ -106,7 +105,7 @@ dependencies {
includeInJar "dev.kastle.webrtc:webrtc-java:1.0.3:$it"
}
includeInJar "gs.mclo:api:6.2.1"
- includeInJar "net.lenni0451:optconfig:1.1.1"
+ includeInJar "net.lenni0451.optconfig:cli:2.0.0-20260504.192228-9"
includeInJarJ8(compileOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader:1.3.6"))
includeInJarJ8 "xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:1.3.6:downgraded-8"
diff --git a/src/main/java/net/raphimc/viaproxy/ViaProxy.java b/src/main/java/net/raphimc/viaproxy/ViaProxy.java
index 33609475..8ddae7a9 100644
--- a/src/main/java/net/raphimc/viaproxy/ViaProxy.java
+++ b/src/main/java/net/raphimc/viaproxy/ViaProxy.java
@@ -44,6 +44,7 @@
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
import net.raphimc.viaproxy.plugins.events.ViaProxyLoadedEvent;
import net.raphimc.viaproxy.protocoltranslator.ProtocolTranslator;
+import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyCLIConfig;
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer;
import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyHandler;
@@ -232,7 +233,11 @@ public static void injectedMain(final String injectionMethod, final String[] arg
viaProxyConfigFile = new File(ViaProxy.getCwd(), "viaproxy.yml");
}
final boolean firstStart = !viaProxyConfigFile.exists();
- CONFIG = ViaProxyConfig.create(viaProxyConfigFile);
+ if (useCLI) {
+ CONFIG = ViaProxyCLIConfig.create(viaProxyConfigFile);
+ } else {
+ CONFIG = ViaProxyConfig.create(viaProxyConfigFile);
+ }
if (useUI) {
progressConsumer.accept("Loading GUI");
@@ -256,7 +261,7 @@ public static void injectedMain(final String injectionMethod, final String[] arg
final String[] cliArgs = new String[args.length - 1];
System.arraycopy(args, 1, cliArgs, 0, cliArgs.length);
try {
- CONFIG.loadFromArguments(cliArgs);
+ ((ViaProxyCLIConfig) CONFIG).loadFromArguments(cliArgs);
} catch (Throwable e) {
throw new RuntimeException("Failed to load CLI arguments", e);
}
@@ -284,8 +289,8 @@ public static void startProxy() {
Logger.LOGGER.info("Starting proxy server");
currentProxyServer = new NetServer(new Client2ProxyChannelInitializer(() -> EVENT_MANAGER.call(new Client2ProxyHandlerCreationEvent(new Client2ProxyHandler(), false)).getHandler()));
EVENT_MANAGER.call(new ProxyStartEvent());
- Logger.LOGGER.info("Binding proxy server to " + AddressUtil.toString(CONFIG.getBindAddress()));
- currentProxyServer.bind(CONFIG.getBindAddress(), false);
+ Logger.LOGGER.info("Binding proxy server to " + AddressUtil.toString(CONFIG.getFrontend().getBindAddress()));
+ currentProxyServer.bind(CONFIG.getFrontend().getBindAddress(), false);
} catch (Throwable e) {
currentProxyServer = null;
throw e;
diff --git a/src/main/java/net/raphimc/viaproxy/cli/BetterHelpFormatter.java b/src/main/java/net/raphimc/viaproxy/cli/BetterHelpFormatter.java
deleted file mode 100644
index cc8c7a46..00000000
--- a/src/main/java/net/raphimc/viaproxy/cli/BetterHelpFormatter.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
- * Copyright (C) 2021-2026 RK_01/RaphiMC and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package net.raphimc.viaproxy.cli;
-
-import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
-import joptsimple.BuiltinHelpFormatter;
-import joptsimple.OptionDescriptor;
-import joptsimple.internal.Classes;
-import joptsimple.internal.Strings;
-
-import java.util.List;
-
-public class BetterHelpFormatter extends BuiltinHelpFormatter {
-
- public BetterHelpFormatter() {
- super(120, 2);
- }
-
- @Override
- protected String extractTypeIndicator(OptionDescriptor descriptor) {
- String indicator = descriptor.argumentTypeIndicator();
- if (indicator != null) {
- indicator = indicator.substring(indicator.indexOf('$') + 1);
- if (indicator.startsWith("[")) {
- return indicator.substring(1, indicator.length() - 1);
- }
- }
- return !Strings.isNullOrEmpty(indicator) && !String.class.getName().equals(indicator) ? Classes.shortNameOf(indicator) : "String";
- }
-
- @Override
- protected String createDefaultValuesDisplay(List> defaultValues) {
- if (defaultValues.size() == 1) {
- Object value = defaultValues.get(0);
- if (value instanceof ProtocolVersion version) {
- return version.getName();
- } else if (value instanceof String) {
- return "\"" + value + "\"";
- }
-
- return value.toString();
- }
-
- return defaultValues.toString();
- }
-
-}
diff --git a/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java b/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java
index 9731763a..cf4734b9 100644
--- a/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java
+++ b/src/main/java/net/raphimc/viaproxy/cli/ConsoleFormatter.java
@@ -17,88 +17,22 @@
*/
package net.raphimc.viaproxy.cli;
-public class ConsoleFormatter {
-
- private static final String PREFIX = "\033[";
- private static final String SUFFIX = "m";
+import net.lenni0451.mcstructs.text.TextComponent;
+import net.lenni0451.mcstructs.text.stringformat.StringFormat;
+import net.lenni0451.mcstructs.text.stringformat.handling.ColorHandling;
+import net.lenni0451.mcstructs.text.stringformat.handling.SerializerUnknownHandling;
- public static String convert(final String s) {
- StringBuilder out = new StringBuilder();
- char[] chars = s.toCharArray();
- for (int i = 0; i < chars.length; i++) {
- char c = chars[i];
- char next = i + 1 < chars.length ? chars[i + 1] : '\0';
- if (c == '§') {
- if (next != '\0') {
- if (isColor(next)) out.append(convertToAnsi('r'));
- out.append(convertToAnsi(next));
- i++;
- }
- } else {
- out.append(c);
- }
- }
- return out + convertToAnsi('r');
- }
+public class ConsoleFormatter {
- //The ANSI codes are an approximation. True color is used to get the exact color.
- private static String convertToAnsi(final char color) {
- switch (Character.toLowerCase(color)) {
- case '0': //Black
- return PREFIX + getColor("30", 0x00_00_00) + SUFFIX;
- case '1': //Dark Blue
- return PREFIX + getColor("34", 0x00_00_AA) + SUFFIX;
- case '2': //Dark Green
- return PREFIX + getColor("32", 0x00_AA_00) + SUFFIX;
- case '3': //Dark Aqua
- return PREFIX + getColor("36", 0x00_AA_AA) + SUFFIX;
- case '4': //Dark Red
- return PREFIX + getColor("31", 0xAA_00_00) + SUFFIX;
- case '5': //Dark Purple
- return PREFIX + getColor("35", 0xAA_00_AA) + SUFFIX;
- case '6': //Gold
- return PREFIX + getColor("33", 0xFF_AA_00) + SUFFIX;
- case '7': //Gray
- return PREFIX + getColor("37", 0xAA_AA_AA) + SUFFIX;
- case '8': //Dark Gray
- return PREFIX + getColor("90", 0x55_55_55) + SUFFIX;
- case '9': //Blue
- return PREFIX + getColor("94", 0x55_55_FF) + SUFFIX;
- case 'a': //Green
- return PREFIX + getColor("92", 0x55_FF_55) + SUFFIX;
- case 'b': //Aqua
- return PREFIX + getColor("96", 0x55_FF_FF) + SUFFIX;
- case 'c': //Red
- return PREFIX + getColor("91", 0xFF_55_55) + SUFFIX;
- case 'd': //Light Purple
- return PREFIX + getColor("95", 0xFF_55_FF) + SUFFIX;
- case 'e': //Yellow
- return PREFIX + getColor("93", 0xFF_FF_55) + SUFFIX;
- case 'f': //White
- return PREFIX + getColor("97", 0xFF_FF_FF) + SUFFIX;
- case 'k': //Obfuscated
- return ""; //Not supported in terminal
- case 'l': //Bold
- return PREFIX + "1" + SUFFIX;
- case 'm': //Strikethrough
- return PREFIX + "9" + SUFFIX;
- case 'n': //Underline
- return PREFIX + "4" + SUFFIX;
- case 'o': //Italic
- return PREFIX + "3" + SUFFIX;
- case 'r': //Reset
- default:
- return PREFIX + 0 + SUFFIX;
- }
- }
+ private static final StringFormat VANILLA_FORMAT = StringFormat.vanilla();
+ private static final StringFormat ANSI_FORMAT = StringFormat.ansi();
- private static boolean isColor(char color) {
- color = Character.toLowerCase(color);
- return color >= '0' && color <= '9' || color >= 'a' && color <= 'f';
+ public static String convert(final String legacyString) {
+ return VANILLA_FORMAT.convertTo(legacyString, ANSI_FORMAT);
}
- private static String getColor(final String ansi, final int rgb) {
- return String.format("38;2;%d;%d;%d", (rgb >> 16) & 255, (rgb >> 8) & 255, rgb & 255);
+ public static String convert(final TextComponent component) {
+ return ANSI_FORMAT.toString(component, ColorHandling.RESET, SerializerUnknownHandling.IGNORE);
}
}
diff --git a/src/main/java/net/raphimc/viaproxy/cli/HelpRequestedException.java b/src/main/java/net/raphimc/viaproxy/cli/HelpRequestedException.java
deleted file mode 100644
index 572afbb7..00000000
--- a/src/main/java/net/raphimc/viaproxy/cli/HelpRequestedException.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
- * Copyright (C) 2021-2026 RK_01/RaphiMC and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package net.raphimc.viaproxy.cli;
-
-public class HelpRequestedException extends RuntimeException {
-}
diff --git a/src/main/java/net/raphimc/viaproxy/cli/command/impl/AccountCommand.java b/src/main/java/net/raphimc/viaproxy/cli/command/impl/AccountCommand.java
index 14f2ee1c..27b41418 100644
--- a/src/main/java/net/raphimc/viaproxy/cli/command/impl/AccountCommand.java
+++ b/src/main/java/net/raphimc/viaproxy/cli/command/impl/AccountCommand.java
@@ -55,14 +55,14 @@ public void register(LiteralArgumentBuilder builder) {
ctx.getSource().sendMessage("No accounts added yet.");
} else {
for (int i = 0; i < accounts.size(); i++) {
- boolean isSelected = ViaProxy.getConfig().getAccount() == accounts.get(i);
+ boolean isSelected = ViaProxy.getConfig().getProxy().getAccount() == accounts.get(i);
ctx.getSource().sendMessage("[" + i + "] " + accounts.get(i).getDisplayString() + (isSelected ? " <--" : ""));
}
}
return 1;
}));
builder.then(literal("deselect").executes(ctx -> {
- ViaProxy.getConfig().setAccount(null);
+ ViaProxy.getConfig().getProxy().setAccount(null);
ctx.getSource().sendMessage("Deselected current account.");
return 1;
}));
@@ -78,7 +78,7 @@ public void register(LiteralArgumentBuilder builder) {
return 0;
}
Account account = ViaProxy.getSaveManager().accountsSave.getAccounts().get(index);
- ViaProxy.getConfig().setAccount(account);
+ ViaProxy.getConfig().getProxy().setAccount(account);
ctx.getSource().sendMessage("Selected account " + index + ": " + account.getDisplayString() + ".");
return 1;
})));
@@ -111,8 +111,8 @@ public void register(LiteralArgumentBuilder builder) {
Account account = ViaProxy.getSaveManager().accountsSave.getAccounts().get(index);
ViaProxy.getSaveManager().accountsSave.removeAccount(account);
ViaProxy.getSaveManager().save();
- if (ViaProxy.getConfig().getAccount() == account) {
- ViaProxy.getConfig().setAccount(null);
+ if (ViaProxy.getConfig().getProxy().getAccount() == account) {
+ ViaProxy.getConfig().getProxy().setAccount(null);
}
ctx.getSource().sendMessage("Removed account " + index + ": " + account.getDisplayString() + ".");
return 1;
diff --git a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocol1_20To1_20_2.java b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocol1_20To1_20_2.java
index 8a71bb6e..1acf3094 100644
--- a/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocol1_20To1_20_2.java
+++ b/src/main/java/net/raphimc/viaproxy/injection/mixins/MixinProtocol1_20To1_20_2.java
@@ -32,7 +32,7 @@ public abstract class MixinProtocol1_20To1_20_2 {
@Inject(method = "lambda$queueServerboundPacket$12", at = @At("HEAD"), cancellable = true)
private static void dontQueueConfigPackets(ServerboundPackets1_20_2 packetType, PacketWrapper wrapper, CallbackInfo ci) {
- if (ViaProxy.getConfig().shouldSkipConfigStatePacketQueue()) {
+ if (ViaProxy.getConfig().getProxy().shouldSkipConfigStatePacketQueue()) {
ci.cancel();
switch (packetType) {
case CUSTOM_PAYLOAD -> wrapper.setPacketType(ServerboundPackets1_19_4.CUSTOM_PAYLOAD);
diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/PostOptionsParseEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/PostOptionsParseEvent.java
deleted file mode 100644
index e80ef24a..00000000
--- a/src/main/java/net/raphimc/viaproxy/plugins/events/PostOptionsParseEvent.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
- * Copyright (C) 2021-2026 RK_01/RaphiMC and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package net.raphimc.viaproxy.plugins.events;
-
-import joptsimple.OptionSet;
-
-public class PostOptionsParseEvent {
-
- private final OptionSet options;
-
- public PostOptionsParseEvent(final OptionSet options) {
- this.options = options;
- }
-
- public OptionSet getOptions() {
- return this.options;
- }
-
-}
diff --git a/src/main/java/net/raphimc/viaproxy/plugins/events/PreOptionsParseEvent.java b/src/main/java/net/raphimc/viaproxy/plugins/events/PreOptionsParseEvent.java
deleted file mode 100644
index 4b1bce34..00000000
--- a/src/main/java/net/raphimc/viaproxy/plugins/events/PreOptionsParseEvent.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
- * Copyright (C) 2021-2026 RK_01/RaphiMC and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package net.raphimc.viaproxy.plugins.events;
-
-import joptsimple.OptionParser;
-
-public class PreOptionsParseEvent {
-
- private final OptionParser parser;
-
- public PreOptionsParseEvent(final OptionParser parser) {
- this.parser = parser;
- }
-
- public OptionParser getParser() {
- return this.parser;
- }
-
-}
diff --git a/src/main/java/net/raphimc/viaproxy/protocoltranslator/impl/ViaProxyViaCodec.java b/src/main/java/net/raphimc/viaproxy/protocoltranslator/impl/ViaProxyViaCodec.java
index 3fdb636c..21c2c3bd 100644
--- a/src/main/java/net/raphimc/viaproxy/protocoltranslator/impl/ViaProxyViaCodec.java
+++ b/src/main/java/net/raphimc/viaproxy/protocoltranslator/impl/ViaProxyViaCodec.java
@@ -31,7 +31,7 @@ public ViaProxyViaCodec(UserConnection user) {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- if (ViaProxy.getConfig().shouldIgnoreProtocolTranslationErrors()) {
+ if (ViaProxy.getConfig().getAdvanced().shouldIgnoreProtocolTranslationErrors()) {
try {
super.channelRead(ctx, msg);
} catch (Throwable e) {
diff --git a/src/main/java/net/raphimc/viaproxy/protocoltranslator/providers/ViaProxyClassicMPPassProvider.java b/src/main/java/net/raphimc/viaproxy/protocoltranslator/providers/ViaProxyClassicMPPassProvider.java
index b2e1f33b..5bb74747 100644
--- a/src/main/java/net/raphimc/viaproxy/protocoltranslator/providers/ViaProxyClassicMPPassProvider.java
+++ b/src/main/java/net/raphimc/viaproxy/protocoltranslator/providers/ViaProxyClassicMPPassProvider.java
@@ -27,6 +27,7 @@
import net.raphimc.vialegacy.protocol.release.r1_2_4_5tor1_3_1_2.provider.OldAuthProvider;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
+import net.raphimc.viaproxy.saves.impl.accounts.ClassicAccount;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
@@ -36,10 +37,9 @@ public class ViaProxyClassicMPPassProvider extends ClassicMPPassProvider {
@Override
public String getMpPass(UserConnection user) {
- final String mppass = ProxyConnection.fromUserConnection(user).getUserOptions().classicMpPass();
- if (mppass != null && !mppass.isBlank()) {
- return mppass;
- } else if (ViaProxy.getConfig().useBetacraftAuth()) {
+ if (ProxyConnection.fromUserConnection(user).getUserOptions().account() instanceof ClassicAccount classicAccount) {
+ return classicAccount.getMppass();
+ } else if (ViaProxy.getConfig().getBackend().useBetaCraftAuth()) {
try {
final HttpClient httpClient = new HttpClient();
String externalIp;
diff --git a/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ConsoleCommandSender.java b/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ConsoleCommandSender.java
index a1a279af..0f047e49 100644
--- a/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ConsoleCommandSender.java
+++ b/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ConsoleCommandSender.java
@@ -28,12 +28,12 @@ public class ConsoleCommandSender implements ViaCommandSender {
private static final UUID CONSOLE_UUID = new UUID(0, 0);
@Override
- public boolean hasPermission(String permission) {
+ public boolean hasPermission(final String permission) {
return true;
}
@Override
- public void sendMessage(String msg) {
+ public void sendMessage(final String msg) {
Via.getPlatform().getLogger().info(ConsoleFormatter.convert(msg));
}
diff --git a/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyCLIConfig.java b/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyCLIConfig.java
new file mode 100644
index 00000000..71047843
--- /dev/null
+++ b/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyCLIConfig.java
@@ -0,0 +1,105 @@
+/*
+ * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
+ * Copyright (C) 2021-2026 RK_01/RaphiMC and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package net.raphimc.viaproxy.protocoltranslator.viaproxy;
+
+import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
+import net.lenni0451.optconfig.CLIConfigLoader;
+import net.lenni0451.optconfig.ConfigContext;
+import net.lenni0451.optconfig.ConfigLoader;
+import net.lenni0451.optconfig.annotations.*;
+import net.lenni0451.optconfig.cli.model.HelpOptions;
+import net.lenni0451.optconfig.cli.model.LoadedOptions;
+import net.lenni0451.optconfig.exceptions.CLIMissingOptionValueException;
+import net.lenni0451.optconfig.exceptions.CLIParserException;
+import net.lenni0451.optconfig.exceptions.InvalidSerializedObjectException;
+import net.lenni0451.optconfig.provider.ConfigProvider;
+import net.raphimc.viaproxy.util.logging.Logger;
+
+import java.io.File;
+
+@OptConfig
+@CheckSuperclass(useParentAnnotation = true)
+public class ViaProxyCLIConfig extends ViaProxyConfig {
+
+ private ConfigContext configContext;
+
+ @Hidden
+ @Option("help")
+ @Description("Displays a help message with a list of all available options")
+ private boolean help = false;
+
+ @Hidden
+ @Option("extended-help")
+ @Description("Displays an extended help message with detailed descriptions of the options")
+ private boolean extendedHelp = false;
+
+ @Hidden
+ @Option("list-versions")
+ @Description("Lists all supported backend server versions and exits")
+ private boolean listVersions = false;
+
+ public static ViaProxyCLIConfig create(final File configFile) {
+ final ConfigLoader configLoader = new ConfigLoader<>(ViaProxyCLIConfig.class);
+ configLoader.getConfigOptions().setRewriteConfig(true).setDeserializerExceptionHandler((option, exception) -> Logger.LOGGER.warn("Failed to deserialize option '{}'. Resetting it. Error: {}", option, exception.getMessage()));
+ try {
+ return configLoader.load(ConfigProvider.file(configFile)).getConfigInstance();
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed to load config", e);
+ }
+ }
+
+ public void loadFromArguments(final String[] args) {
+ this.configContext.getConfigLoader().getConfigOptions().setResetInvalidOptions(false);
+ final CLIConfigLoader cliConfigLoader = new CLIConfigLoader<>(this.configContext);
+ try {
+ final LoadedOptions loadedOptions = cliConfigLoader.loadCLIOptions(args, true);
+ if (this.help || this.extendedHelp) {
+ throw new HelpRequestedException();
+ } else if (this.listVersions) {
+ Logger.LOGGER.info("=== Supported backend server versions ===");
+ for (ProtocolVersion version : ProtocolVersion.getProtocols()) {
+ Logger.LOGGER.info(version.getName());
+ }
+ Logger.LOGGER.info("===================================");
+ System.exit(0);
+ }
+
+ if (loadedOptions.options().contains("minecraft-account-index") && !loadedOptions.options().contains("auth-method")) {
+ this.getBackend().setAuthMethod(AuthMethod.ACCOUNT);
+ }
+ return;
+ } catch (CLIParserException | CLIMissingOptionValueException | InvalidSerializedObjectException e) {
+ Logger.LOGGER.fatal("Error parsing CLI options: {}", e.getMessage());
+ } catch (HelpRequestedException ignored) {
+ }
+
+ cliConfigLoader.printCLIHelp(Logger.SYSOUT, HelpOptions.DEFAULT.withShowDescription(this.extendedHelp).withShowDepends(false).withShowBooleanType(true));
+ if (!this.extendedHelp) {
+ Logger.LOGGER.info("For a more detailed description of the options, use --extended-help or refer to the viaproxy.yml file.");
+ }
+ System.exit(1);
+ }
+
+ @Override
+ public void save() {
+ }
+
+ private static class HelpRequestedException extends Exception {
+ }
+
+}
diff --git a/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyConfig.java b/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyConfig.java
index 06e50c0a..71c70499 100644
--- a/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyConfig.java
+++ b/src/main/java/net/raphimc/viaproxy/protocoltranslator/viaproxy/ViaProxyConfig.java
@@ -18,27 +18,16 @@
package net.raphimc.viaproxy.protocoltranslator.viaproxy;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
-import joptsimple.OptionException;
-import joptsimple.OptionParser;
-import joptsimple.OptionSet;
-import joptsimple.OptionSpec;
import net.lenni0451.optconfig.ConfigContext;
import net.lenni0451.optconfig.ConfigLoader;
import net.lenni0451.optconfig.annotations.*;
-import net.lenni0451.optconfig.index.ClassIndexer;
-import net.lenni0451.optconfig.index.ConfigType;
-import net.lenni0451.optconfig.index.types.ConfigOption;
-import net.lenni0451.optconfig.index.types.SectionIndex;
+import net.lenni0451.optconfig.annotations.cli.CLIAliases;
+import net.lenni0451.optconfig.annotations.cli.CLIName;
+import net.lenni0451.optconfig.migrate.ConfigMigrator;
import net.lenni0451.optconfig.provider.ConfigProvider;
-import net.raphimc.viaproxy.ViaProxy;
-import net.raphimc.viaproxy.cli.BetterHelpFormatter;
-import net.raphimc.viaproxy.cli.HelpRequestedException;
-import net.raphimc.viaproxy.plugins.events.PostOptionsParseEvent;
-import net.raphimc.viaproxy.plugins.events.PreOptionsParseEvent;
import net.raphimc.viaproxy.protocoltranslator.ProtocolTranslator;
import net.raphimc.viaproxy.saves.impl.accounts.Account;
import net.raphimc.viaproxy.util.AddressUtil;
-import net.raphimc.viaproxy.util.Proxy;
import net.raphimc.viaproxy.util.config.*;
import net.raphimc.viaproxy.util.logging.Logger;
@@ -46,186 +35,32 @@
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
-import java.util.Stack;
-@OptConfig(header = "ViaProxy configuration file", version = 1)
+@OptConfig(header = "ViaProxy configuration file", version = 2)
+@Migrator(from = 1, to = 2, migrator = ViaProxyConfig.Migrator1To2.class)
public class ViaProxyConfig {
private ConfigContext configContext;
- @NotReloadable
- @Option("bind-address")
- @Description("The address ViaProxy should listen for connections.")
- @TypeSerializer(SocketAddressTypeSerializer.class)
- private SocketAddress bindAddress = AddressUtil.parse("0.0.0.0:25568", null);
-
- @Option(value = "target-address", dependencies = "target-version")
- @Description("The address of the server ViaProxy should connect to.")
- @TypeSerializer(TargetAddressTypeSerializer.class)
- private SocketAddress targetAddress = AddressUtil.parse("127.0.0.1:25565", null);
-
- @Option("target-version")
- @Description("The version ViaProxy should translate to. (See ViaProxy GUI for a list of versions)")
- @TypeSerializer(ProtocolVersionTypeSerializer.class)
- private ProtocolVersion targetVersion = ProtocolTranslator.AUTO_DETECT_PROTOCOL;
-
- @Option("connect-timeout")
- @Description("The connect timeout for backend server connections in milliseconds.")
- private int connectTimeout = 8000;
-
- @Option("proxy-online-mode")
- @Description({
- "Proxy Online Mode allows you to see skins on online mode servers and use the signed chat features.",
- "Enabling Proxy Online Mode requires your client to have a valid minecraft account."
- })
- private boolean proxyOnlineMode = false;
-
- @Option("auth-method")
- @Description({
- "The authentication method to use for joining the target server.",
- "none: No authentication (Offline mode)",
- "account: Use an account for joining the target server. (Has to be configured in ViaProxy GUI)"
- })
- private AuthMethod authMethod = AuthMethod.NONE;
-
- @Option(value = "minecraft-account-index", dependencies = "auth-method")
- @Description("The GUI account list index (0 indexed) of the account if the auth method is set to account.")
- @TypeSerializer(AccountTypeSerializer.class)
- private Account account = null;
-
- @Option("betacraft-auth")
- @Description({
- "Use BetaCraft authentication for classic servers.",
- "Enabling BetaCraft Auth allows you to join classic servers which have online mode enabled."
- })
- private boolean betacraftAuth = false;
-
- @Option("backend-proxy-url")
- @Description({
- "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for backend server connections. Leave empty to connect directly.",
- "Supported formats:",
- "- type://address:port",
- "- type://username:password@address:port"
- })
- @TypeSerializer(ProxyTypeSerializer.class)
- private Proxy backendProxy = null;
-
- @Option("backend-haproxy")
- @Description("Send HAProxy protocol messages to the target server.")
- private boolean backendHaProxy = false;
-
- @Option("frontend-haproxy")
- @Description("Read HAProxy protocol messages from client connections.")
- private boolean frontendHaProxy = false;
-
- @Option("chat-signing")
- @Description("Enables sending signed chat messages on >= 1.19 servers.")
- private boolean chatSigning = true;
-
- @Option("compression-threshold")
- @Description("The threshold for packet compression. Packets larger than this size will be compressed. (-1 to disable)")
- private int compressionThreshold = 256;
-
- @Option("allow-beta-pinging")
- @Description("Enabling this will allow you to ping <= b1.7.3 servers. This may cause issues with servers that block too frequent connections.")
- private boolean allowBetaPinging = false;
-
- @Option("ignore-protocol-translation-errors")
- @Description({
- "Enabling this will prevent getting disconnected from the server when a packet translation error occurs and instead only print the error in the console.",
- "This may cause issues depending on the type of packet which failed to translate."
- })
- private boolean ignoreProtocolTranslationErrors = false;
-
- @Option("suppress-client-protocol-errors")
- @Description({
- "Enabling this will suppress client protocol errors to prevent lag when ViaProxy is getting spammed with invalid packets.",
- "This may cause issues with debugging client connection issues because no error messages will be printed."
- })
- private boolean suppressClientProtocolErrors = false;
-
- @Option("allow-legacy-client-passthrough")
- @Description("Allow <= 1.6.4 clients to connect through ViaProxy to the target server. (No protocol translation or packet handling)")
- private boolean allowLegacyClientPassthrough = false;
-
- @Option("bungeecord-player-info-passthrough")
- @Description({
- "Allow additional information like player ip, player uuid to be passed through to the backend server.",
- "This is typically used by proxies like BungeeCord and requires support from the backend server as well."
- })
- private boolean bungeecordPlayerInfoPassthrough = false;
-
- @Option("rewrite-handshake-packet")
- @Description({
- "Enabling this will rewrite the address in the handshake packet to a value the vanilla client would have sent when connecting directly to the target server.",
- "This should be left enabled unless you are a server owner and you need the original address on the backend server."
- })
- private boolean rewriteHandshakePacket = true;
-
- @Option("rewrite-transfer-packets")
- @Description({
- "Enabling this will rewrite transfer packets to point back to ViaProxy. This allows ViaProxy to perform protocol translation when forwarding the player to the actual server from the transfer packet.",
- "This should be left enabled unless you are a server owner and the servers you are transferring to perform their own protocol translation."
- })
- private boolean rewriteTransferPackets = true;
-
- @Option("custom-motd")
- @Description("Custom MOTD to send when clients ping the proxy. Leave empty to use the target server's MOTD.")
- private String customMotd = "";
-
- @Option("custom-favicon-path")
- @Description("Relative file path to a custom favicon to send when clients ping the proxy. Leave empty to use the target server's favicon.")
- private String customFaviconPath = "";
-
- @Option("resource-pack-url")
- @Description({"URL of a resource pack which clients can optionally download when connecting to the server. Leave empty to disable.", "Example: http://example.com/resourcepack.zip"})
- private String resourcePackUrl = "";
-
- @Option("wildcard-domain-handling")
- @Description({
- "Allows clients to specify a target server and version using wildcard domains.",
- "none: No wildcard domain handling.",
- "public: Public wildcard domain handling. Intended for usage by external clients. (Example: address_port_version.viaproxy.127.0.0.1.nip.io)",
- "internal: Internal wildcard domain handling. Intended for local usage by custom clients. (Example: original-handshake-address\\7address:port\\7version\\7classic-mppass)"
- })
- private WildcardDomainHandling wildcardDomainHandling = WildcardDomainHandling.NONE;
-
- @Option("simple-voice-chat-support")
- @Description("Enables handling and rewriting of Simple Voice Chat mod packets.")
- private boolean simpleVoiceChatSupport = false;
-
- @Option("fix-fabric-particle-api")
- @Description({
- "Fixes an issue where the Fabric Particle API causes disconnects when both the client and server have the mod installed and both are 1.21.5+.",
- "See https://github.com/ViaVersion/ViaFabric/issues/428"
- })
- private boolean fixFabricParticleApi = true;
-
- @Option("fake-accept-resource-packs")
- @Description({
- "Accepts resource packs from the server without showing a prompt to the client.",
- "This is required for servers that require a resource pack, but the client can't load it due to version differences."
- })
- private boolean fakeAcceptResourcePacks = false;
-
- @Option("skip-config-state-packet-queue")
- @Description({
- "Fixes potential join issues on <= 1.20.1 quilt/fabric servers.",
- "It's recommended to only enable this if you are experiencing issues with the config state packet queue (See above issue)."
- })
- private boolean skipConfigStatePacketQueue = false;
-
- @Option("log-ips")
- @Description("Disable this if you want to hide IP addresses in the console and log files.")
- private boolean logIps = true;
-
- @Option("log-client-status-requests")
- @Description("Enable this if you want to see client status requests in the console and log files.")
- private boolean logClientStatusRequests = false;
+ @Option("frontend")
+ @Description("These options affect the behavior of the proxy related to client connections.")
+ private final Frontend frontend = new Frontend();
+
+ @Option("proxy")
+ @Description("These options affect the general behavior of the proxy.")
+ private final Proxy proxy = new Proxy();
+
+ @Option("backend")
+ @Description("These options affect the behavior of the proxy related to server connections.")
+ private final Backend backend = new Backend();
+
+ @Option("advanced")
+ @Description("These options are for advanced users and typically shouldn't be changed.")
+ private final Advanced advanced = new Advanced();
public static ViaProxyConfig create(final File configFile) {
final ConfigLoader configLoader = new ConfigLoader<>(ViaProxyConfig.class);
- configLoader.getConfigOptions().setResetInvalidOptions(true).setRewriteConfig(true).setCommentSpacing(1);
+ configLoader.getConfigOptions().setRewriteConfig(true).setDeserializerExceptionHandler((option, exception) -> Logger.LOGGER.warn("Failed to deserialize option '{}'. Resetting it. Error: {}", option, exception.getMessage()));
try {
return configLoader.load(ConfigProvider.file(configFile)).getConfigInstance();
} catch (Throwable e) {
@@ -233,406 +68,953 @@ public static ViaProxyConfig create(final File configFile) {
}
}
- @SuppressWarnings("UnstableApiUsage")
- public void loadFromArguments(final String[] args) throws Exception {
- final OptionParser optionParser = new OptionParser();
- final OptionSpec optionHelp = optionParser.accepts("help").forHelp();
- final OptionSpec optionListVersions = optionParser.accepts("list-versions", "Lists all supported server/target versions").forHelp();
+ public void save() {
+ try {
+ this.configContext.save();
+ } catch (Throwable e) {
+ throw new RuntimeException("Failed to save config", e);
+ }
+ }
- final Map, ConfigOption> optionMap = new HashMap<>();
- final Stack stack = new Stack<>();
- final ConfigLoader configLoader = new ConfigLoader<>(ViaProxyConfig.class);
- stack.push(ClassIndexer.indexClass(ConfigType.INSTANCED, ViaProxyConfig.class, configLoader.getConfigOptions().getClassAccessFactory()));
- while (!stack.isEmpty()) {
- final SectionIndex index = stack.pop();
- stack.addAll(index.getSubSections().values());
+ public Frontend getFrontend() {
+ return this.frontend;
+ }
+
+ public Proxy getProxy() {
+ return this.proxy;
+ }
+
+ public Backend getBackend() {
+ return this.backend;
+ }
+
+ public Advanced getAdvanced() {
+ return this.advanced;
+ }
+
+ @Section
+ public class Frontend {
+
+ @Option("bind-address")
+ @CLIName(value = "bind-address", omitSection = true)
+ @Description("The address on which ViaProxy should listen for clients.")
+ @TypeSerializer(SocketAddressTypeSerializer.class)
+ private SocketAddress bindAddress = AddressUtil.parse("0.0.0.0:25568", null);
+
+ @Option("online-mode")
+ @CLIName(value = "frontend-online-mode", omitSection = true)
+ @CLIAliases(value = "proxy-online-mode", hidden = true)
+ @Description({
+ "Enabling Online Mode allows clients see skins on online mode servers and use the signed chat features.",
+ "This requires clients to use a valid Minecraft account."
+ })
+ private boolean onlineMode = false;
+
+ @Option("haproxy")
+ @CLIName(value = "frontend-haproxy", omitSection = true)
+ @Description("Read HAProxy protocol messages from clients.")
+ private boolean haProxy = false;
+
+ @Option("compression-threshold")
+ @CLIName(value = "compression-threshold", omitSection = true)
+ @Description("The threshold for packet compression. Packets larger than this size will be compressed. (-1 to disable)")
+ private int compressionThreshold = 256;
+
+ @Option("suppress-packet-errors")
+ @CLIName(value = "suppress-client-packet-errors", omitSection = true)
+ @CLIAliases(value = "suppress-client-protocol-errors", hidden = true)
+ @Description({
+ "Enabling this will suppress packet errors to prevent lag when ViaProxy is getting spammed with invalid packets.",
+ "This may cause issues with debugging client connection issues because no error messages will be printed."
+ })
+ private boolean suppressPacketErrors = false;
+
+ @Option("motd")
+ @Description("These options allow you to override parts of the backend server's MotD.")
+ private final MotD motd = new MotD();
+
+ @Option("resource-pack-url")
+ @CLIName(value = "resource-pack-url", omitSection = true)
+ @Description({
+ "URL of a resource pack which will be sent to clients. Leave empty to disable.",
+ "Example: https://example.com/resourcepack.zip"
+ })
+ private String resourcePackUrl = "";
+
+ @Option("wildcard-domain-handling")
+ @CLIName(value = "wildcard-domain-handling", omitSection = true)
+ @Description({
+ "Allows clients to specify a target server and version using wildcard domains.",
+ "none: No wildcard domain handling.",
+ "public: Public wildcard domain handling. Intended for usage by external clients. (Example: address_port_version.viaproxy.127.0.0.1.nip.io)",
+ "internal: Internal wildcard domain handling. Intended for local usage by custom clients. (Example: original-handshake-address\\7address:port\\7version)"
+ })
+ private WildcardDomainHandling wildcardDomainHandling = WildcardDomainHandling.NONE;
+
+ @Option("log-client-status-requests")
+ @CLIName(value = "log-client-status-requests", omitSection = true)
+ @Description("Enable this if you want to see client status requests in the console and log files.")
+ private boolean logClientStatusRequests = false;
+
+ public SocketAddress getBindAddress() {
+ return this.bindAddress;
+ }
- for (ConfigOption option : index.getOptions()) {
- if (index.getSubSections().containsKey(option)) continue;
+ public void setBindAddress(final SocketAddress bindAddress) {
+ this.bindAddress = bindAddress;
+ ViaProxyConfig.this.save();
+ }
- Object defaultValue = option.getFieldAccess().getValue(this);
- if (option.getTypeSerializer() != null) {
- defaultValue = option.createTypeSerializer(configLoader, ViaProxyConfig.class, this).serialize(defaultValue);
- }
- final OptionSpec