Compare commits

...

10 Commits

Author SHA1 Message Date
dependabot[bot] e84adefc42
Merge e3471e6723 into e105ad9d34 2024-05-05 15:36:28 +00:00
RaphiMC e105ad9d34
Bump version to 3.2.3-SNAPSHOT 2024-05-05 17:34:08 +02:00
RaphiMC 01c94f882d
Release 3.2.2 2024-05-05 17:25:39 +02:00
Lenni0451 c0d6edc99d
Moved checkboxes to grid panel 2024-05-04 19:19:01 +02:00
RaphiMC eb1f66763e
Moved OpenAuthMod related code into OpenAuthModPacketHandler 2024-05-04 19:15:31 +02:00
RaphiMC 97d15f9c5d
Updated README 2024-05-04 19:04:33 +02:00
RaphiMC 044e1d65a1
Added support for Simple Voice Chat passthrough
Closes #155
2024-05-04 18:59:56 +02:00
RaphiMC f5c330168f
Added support to specify a custom MOTD
Closes #188
2024-05-02 01:09:34 +02:00
RaphiMC 3b9885a055
Improved code 2024-05-01 22:53:49 +02:00
dependabot[bot] e3471e6723
Bump gs.mclo:api from 3.0.1 to 4.0.1
Bumps [gs.mclo:api](https://github.com/aternosorg/mclogs-java) from 3.0.1 to 4.0.1.
- [Release notes](https://github.com/aternosorg/mclogs-java/releases)
- [Commits](https://github.com/aternosorg/mclogs-java/compare/3.0.1...4.0.1)

---
updated-dependencies:
- dependency-name: gs.mclo:api
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 05:11:24 +00:00
18 changed files with 350 additions and 120 deletions

View File

@ -26,6 +26,7 @@ ViaProxy supports joining to any of the listed server version from any of the li
- Support for joining on servers which have chat signing enabled from all listed client versions
- Supports transfer and cookies for <=1.20.4 clients on 1.20.5+ servers
- Allows joining Minecraft Realms with any supported client version
- Supports Simple Voice Chat mod
## Releases
### Executable Jar File

View File

@ -54,11 +54,11 @@ repositories {
}
dependencies {
include "com.viaversion:viaversion-common:4.10.1-SNAPSHOT"
include "com.viaversion:viabackwards-common:4.10.1-SNAPSHOT"
include "com.viaversion:viarewind-common:3.1.1-SNAPSHOT"
include "com.viaversion:viaversion-common:4.10.1"
include "com.viaversion:viabackwards-common:4.10.1"
include "com.viaversion:viarewind-common:3.1.1"
include "net.raphimc:ViaLegacy:2.2.23-SNAPSHOT"
include "net.raphimc:ViaAprilFools:2.0.12-SNAPSHOT"
include "net.raphimc:ViaAprilFools:2.0.13-SNAPSHOT"
include("net.raphimc:ViaBedrock:0.0.7-SNAPSHOT") {
exclude group: "io.netty"
exclude group: "io.jsonwebtoken"
@ -102,7 +102,7 @@ dependencies {
include("org.cloudburstmc.netty:netty-transport-raknet:1.0.0.CR3-SNAPSHOT") {
exclude group: "io.netty"
}
include "gs.mclo:api:3.0.1"
include "gs.mclo:api:4.0.1"
}
sourceSets {

View File

@ -4,4 +4,4 @@ org.gradle.configureondemand=true
maven_group=net.raphimc
maven_name=ViaProxy
maven_version=3.2.2-SNAPSHOT
maven_version=3.2.3-SNAPSHOT

View File

@ -64,8 +64,10 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
private final OptionSpec<Boolean> optionAllowBetaPinging;
private final OptionSpec<Boolean> optionIgnoreProtocolTranslationErrors;
private final OptionSpec<Boolean> optionAllowLegacyClientPassthrough;
private final OptionSpec<String> optionCustomMotd;
private final OptionSpec<String> optionResourcePackUrl;
private final OptionSpec<WildcardDomainHandling> optionWildcardDomainHandling;
private final OptionSpec<Boolean> optionSimpleVoiceChatSupport;
private SocketAddress bindAddress = AddressUtil.parse("0.0.0.0:25568", null);
private SocketAddress targetAddress = AddressUtil.parse("127.0.0.1:25565", null);
@ -82,8 +84,10 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
private boolean allowBetaPinging = false;
private boolean ignoreProtocolTranslationErrors = false;
private boolean allowLegacyClientPassthrough = false;
private String customMotd = "";
private String resourcePackUrl = "";
private WildcardDomainHandling wildcardDomainHandling = WildcardDomainHandling.NONE;
private boolean simpleVoiceChatSupport = false;
public ViaProxyConfig(final File configFile) {
super(configFile);
@ -105,8 +109,10 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
this.optionAllowBetaPinging = this.optionParser.accepts("allow-beta-pinging").withRequiredArg().ofType(Boolean.class).defaultsTo(this.allowBetaPinging);
this.optionIgnoreProtocolTranslationErrors = this.optionParser.accepts("ignore-protocol-translation-errors").withRequiredArg().ofType(Boolean.class).defaultsTo(this.ignoreProtocolTranslationErrors);
this.optionAllowLegacyClientPassthrough = this.optionParser.accepts("allow-legacy-client-passthrough").withRequiredArg().ofType(Boolean.class).defaultsTo(this.allowLegacyClientPassthrough);
this.optionCustomMotd = this.optionParser.accepts("custom-motd").withRequiredArg().ofType(String.class).defaultsTo(this.customMotd);
this.optionResourcePackUrl = this.optionParser.accepts("resource-pack-url").withRequiredArg().ofType(String.class).defaultsTo(this.resourcePackUrl);
this.optionWildcardDomainHandling = this.optionParser.accepts("wildcard-domain-handling").withRequiredArg().ofType(WildcardDomainHandling.class).defaultsTo(this.wildcardDomainHandling);
this.optionSimpleVoiceChatSupport = this.optionParser.accepts("simple-voice-chat-support").withRequiredArg().ofType(Boolean.class).defaultsTo(this.simpleVoiceChatSupport);
}
@Override
@ -135,8 +141,10 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
this.allowBetaPinging = this.getBoolean("allow-beta-pinging", this.allowBetaPinging);
this.ignoreProtocolTranslationErrors = this.getBoolean("ignore-protocol-translation-errors", this.ignoreProtocolTranslationErrors);
this.allowLegacyClientPassthrough = this.getBoolean("allow-legacy-client-passthrough", this.allowLegacyClientPassthrough);
this.customMotd = this.getString("custom-motd", this.customMotd);
this.resourcePackUrl = this.getString("resource-pack-url", this.resourcePackUrl);
this.wildcardDomainHandling = WildcardDomainHandling.byName(this.getString("wildcard-domain-handling", this.wildcardDomainHandling.name()));
this.simpleVoiceChatSupport = this.getBoolean("simple-voice-chat-support", this.simpleVoiceChatSupport);
}
public void loadFromArguments(final String[] args) throws IOException {
@ -169,8 +177,10 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
this.allowBetaPinging = options.valueOf(this.optionAllowBetaPinging);
this.ignoreProtocolTranslationErrors = options.valueOf(this.optionIgnoreProtocolTranslationErrors);
this.allowLegacyClientPassthrough = options.valueOf(this.optionAllowLegacyClientPassthrough);
this.customMotd = options.valueOf(this.optionCustomMotd);
this.resourcePackUrl = options.valueOf(this.optionResourcePackUrl);
this.wildcardDomainHandling = options.valueOf(this.optionWildcardDomainHandling);
this.simpleVoiceChatSupport = options.valueOf(this.optionSimpleVoiceChatSupport);
ViaProxy.EVENT_MANAGER.call(new PostOptionsParseEvent(options));
return;
} catch (OptionException e) {
@ -342,6 +352,15 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
this.set("allow-legacy-client-passthrough", allowLegacyClientPassthrough);
}
public String getCustomMotd() {
return this.customMotd;
}
public void setCustomMotd(final String customMotd) {
this.customMotd = customMotd;
this.set("custom-motd", customMotd);
}
public String getResourcePackUrl() {
return this.resourcePackUrl;
}
@ -360,6 +379,15 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
this.set("wildcard-domain-handling", wildcardDomainHandling.name().toLowerCase(Locale.ROOT));
}
public boolean shouldSupportSimpleVoiceChat() {
return this.simpleVoiceChatSupport;
}
public void setSimpleVoiceChatSupport(final boolean simpleVoiceChatSupport) {
this.simpleVoiceChatSupport = simpleVoiceChatSupport;
this.set("simple-voice-chat-support", simpleVoiceChatSupport);
}
private void checkTargetVersion() {
if (this.targetVersion == null) {
this.targetVersion = ProtocolTranslator.AUTO_DETECT_PROTOCOL;

View File

@ -163,6 +163,9 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
}
if (packet.intendedState.getConnectionState() == ConnectionState.STATUS && !ViaProxy.getConfig().shouldAllowBetaPinging() && serverVersion.olderThanOrEqualTo(LegacyProtocolVersion.b1_7tob1_7_3)) {
if (!ViaProxy.getConfig().getCustomMotd().isBlank()) {
this.proxyConnection.kickClient(ViaProxy.getConfig().getCustomMotd());
}
this.proxyConnection.kickClient("§7ViaProxy is working!\n§7Connect to join the configured server");
}
@ -213,6 +216,9 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
this.proxyConnection.setUserOptions(userOptions);
this.proxyConnection.getPacketHandlers().add(new StatusPacketHandler(this.proxyConnection));
this.proxyConnection.getPacketHandlers().add(new OpenAuthModPacketHandler(this.proxyConnection));
if (ViaProxy.getConfig().shouldSupportSimpleVoiceChat() && serverVersion.newerThan(ProtocolVersion.v1_14) && clientVersion.newerThan(ProtocolVersion.v1_14)) {
this.proxyConnection.getPacketHandlers().add(new SimpleVoiceChatPacketHandler(this.proxyConnection));
}
if (clientVersion.newerThanOrEqualTo(ProtocolVersion.v1_8)) {
this.proxyConnection.getPacketHandlers().add(new BrandCustomPayloadPacketHandler(this.proxyConnection));
}

View File

@ -37,6 +37,7 @@ import net.raphimc.viabedrock.protocol.storage.AuthChainData;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.plugins.events.FillPlayerDataEvent;
import net.raphimc.viaproxy.protocoltranslator.viaproxy.ViaProxyConfig;
import net.raphimc.viaproxy.proxy.packethandler.OpenAuthModPacketHandler;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import net.raphimc.viaproxy.saves.impl.accounts.Account;
import net.raphimc.viaproxy.saves.impl.accounts.BedrockAccount;
@ -109,7 +110,7 @@ public class ExternalInterface {
Logger.u_info("auth", proxyConnection.getC2P().remoteAddress(), proxyConnection.getGameProfile(), "Trying to join online mode server");
if (ViaProxy.getConfig().getAuthMethod() == ViaProxyConfig.AuthMethod.OPENAUTHMOD) {
try {
final ByteBuf response = proxyConnection.sendCustomPayload(OpenAuthModConstants.JOIN_CHANNEL, PacketTypes.writeString(Unpooled.buffer(), serverIdHash)).get(6, TimeUnit.SECONDS);
final ByteBuf response = proxyConnection.getPacketHandler(OpenAuthModPacketHandler.class).sendCustomPayload(OpenAuthModConstants.JOIN_CHANNEL, PacketTypes.writeString(Unpooled.buffer(), serverIdHash)).get(6, TimeUnit.SECONDS);
if (response == null) throw new TimeoutException();
if (response.isReadable() && !response.readBoolean()) throw new TimeoutException();
} catch (TimeoutException e) {
@ -132,7 +133,7 @@ public class ExternalInterface {
if (ViaProxy.getConfig().getAuthMethod() == ViaProxyConfig.AuthMethod.OPENAUTHMOD) {
try {
final ByteBuf response = proxyConnection.sendCustomPayload(OpenAuthModConstants.SIGN_NONCE_CHANNEL, PacketTypes.writeByteArray(Unpooled.buffer(), nonce)).get(5, TimeUnit.SECONDS);
final ByteBuf response = proxyConnection.getPacketHandler(OpenAuthModPacketHandler.class).sendCustomPayload(OpenAuthModConstants.SIGN_NONCE_CHANNEL, PacketTypes.writeByteArray(Unpooled.buffer(), nonce)).get(5, TimeUnit.SECONDS);
if (response == null) throw new TimeoutException();
if (!response.readBoolean()) throw new TimeoutException();
packet.salt = response.readLong();

View File

@ -20,62 +20,44 @@ package net.raphimc.viaproxy.proxy.packethandler;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.util.Key;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.packet.IPacket;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.UnknownPacket;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import java.util.List;
public class BrandCustomPayloadPacketHandler extends PacketHandler {
public class BrandCustomPayloadPacketHandler extends CustomPayloadPacketHandler {
private static final String BRAND_CHANNEL = "minecraft:brand";
private static final String LEGACY_BRAND_CHANNEL = "MC|Brand";
private final int customPayloadId;
private final int configCustomPayloadId;
public BrandCustomPayloadPacketHandler(ProxyConnection proxyConnection) {
super(proxyConnection);
this.customPayloadId = MCPackets.S2C_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion());
this.configCustomPayloadId = MCPackets.S2C_CONFIG_CUSTOM_PAYLOAD.getId(proxyConnection.getClientVersion().getVersion());
}
@Override
public boolean handleP2S(IPacket packet, List<ChannelFutureListener> listeners) {
if (packet instanceof UnknownPacket unknownPacket
&& (unknownPacket.packetId == this.customPayloadId && this.proxyConnection.getP2sConnectionState() == ConnectionState.PLAY
|| unknownPacket.packetId == this.configCustomPayloadId && this.proxyConnection.getP2sConnectionState() == ConnectionState.CONFIGURATION)) {
final ByteBuf data = Unpooled.wrappedBuffer(unknownPacket.data);
final String channel = PacketTypes.readString(data, Short.MAX_VALUE); // channel
if (Key.namespaced(channel).equals(BRAND_CHANNEL) || channel.equals(LEGACY_BRAND_CHANNEL)) {
String brand;
try {
brand = PacketTypes.readString(data, Short.MAX_VALUE);
} catch (Exception e) {
if (this.proxyConnection.getServerVersion().newerThan(ProtocolVersion.v1_20)) {
throw e;
} else { // <=1.20 clients ignore errors
brand = "Unknown";
}
public ByteBuf handleP2S(UnknownPacket packet, String channel, ByteBuf data, List<ChannelFutureListener> listeners) throws Exception {
if (Key.namespaced(channel).equals(BRAND_CHANNEL) || channel.equals(LEGACY_BRAND_CHANNEL)) {
String brand;
try {
brand = PacketTypes.readString(data, Short.MAX_VALUE);
} catch (Exception e) {
if (this.proxyConnection.getServerVersion().newerThan(ProtocolVersion.v1_20)) {
throw e;
} else { // <=1.20 clients ignore errors
brand = "Unknown";
}
final String newBrand = "ViaProxy (" + this.proxyConnection.getClientVersion().getName() + ") -> " + brand + " §r(" + this.proxyConnection.getServerVersion().getName() + ")";
final ByteBuf newCustomPayloadData = Unpooled.buffer();
PacketTypes.writeString(newCustomPayloadData, channel); // channel
PacketTypes.writeString(newCustomPayloadData, newBrand); // data
unknownPacket.data = ByteBufUtil.getBytes(newCustomPayloadData);
}
final String newBrand = "ViaProxy (" + this.proxyConnection.getClientVersion().getName() + ") -> " + brand + " §r(" + this.proxyConnection.getServerVersion().getName() + ")";
final ByteBuf newData = Unpooled.buffer();
PacketTypes.writeString(newData, newBrand);
return newData;
}
return true;
return super.handleP2S(packet, channel, data, listeners);
}
}

View File

@ -22,7 +22,6 @@ import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature;
import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata;
import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_3;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.api.type.types.BitSetType;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
@ -76,7 +75,7 @@ public class ChatSignaturePacketHandler extends PacketHandler {
newChatMessage.writeLong(salt); // salt
Type.OPTIONAL_SIGNATURE_BYTES.write(newChatMessage, signature); // signature
PacketTypes.writeVarInt(newChatMessage, 0); // offset
new BitSetType(20).write(newChatMessage, new BitSet(20)); // acknowledged
Type.ACKNOWLEDGED_BIT_SET.write(newChatMessage, new BitSet(20)); // acknowledged
this.proxyConnection.getChannel().writeAndFlush(newChatMessage).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
return false;

View File

@ -0,0 +1,105 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2024 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 <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.proxy.packethandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.packet.IPacket;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.UnknownPacket;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import java.util.List;
public abstract class CustomPayloadPacketHandler extends PacketHandler {
private final int s2cCustomPayloadId;
private final int s2cConfigCustomPayloadId;
private final int c2sCustomPayloadId;
private final int c2sConfigCustomPayloadId;
public CustomPayloadPacketHandler(ProxyConnection proxyConnection) {
super(proxyConnection);
this.s2cCustomPayloadId = MCPackets.S2C_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion());
this.s2cConfigCustomPayloadId = MCPackets.S2C_CONFIG_CUSTOM_PAYLOAD.getId(proxyConnection.getClientVersion().getVersion());
this.c2sCustomPayloadId = MCPackets.C2S_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion());
this.c2sConfigCustomPayloadId = MCPackets.C2S_CONFIG_CUSTOM_PAYLOAD.getId(proxyConnection.getClientVersion().getVersion());
}
@Override
public boolean handleP2S(IPacket packet, List<ChannelFutureListener> listeners) throws Exception {
if (packet instanceof UnknownPacket unknownPacket
&& (unknownPacket.packetId == this.s2cCustomPayloadId && this.proxyConnection.getP2sConnectionState() == ConnectionState.PLAY
|| unknownPacket.packetId == this.s2cConfigCustomPayloadId && this.proxyConnection.getP2sConnectionState() == ConnectionState.CONFIGURATION)) {
final ByteBuf data = Unpooled.wrappedBuffer(unknownPacket.data);
final String channel = PacketTypes.readString(data, Short.MAX_VALUE); // channel
final ByteBuf newData = this.handleP2S(unknownPacket, channel, data, listeners);
if (newData == data) {
return true;
} else if (newData != null) {
final ByteBuf newCustomPayloadData = Unpooled.buffer();
PacketTypes.writeString(newCustomPayloadData, channel); // channel
newCustomPayloadData.writeBytes(newData);
unknownPacket.data = ByteBufUtil.getBytes(newCustomPayloadData);
return true;
} else {
return false;
}
}
return super.handleP2S(packet, listeners);
}
@Override
public boolean handleC2P(IPacket packet, List<ChannelFutureListener> listeners) throws Exception {
if (packet instanceof UnknownPacket unknownPacket
&& (unknownPacket.packetId == this.c2sCustomPayloadId && this.proxyConnection.getC2pConnectionState() == ConnectionState.PLAY
|| unknownPacket.packetId == this.c2sConfigCustomPayloadId && this.proxyConnection.getC2pConnectionState() == ConnectionState.CONFIGURATION)) {
final ByteBuf data = Unpooled.wrappedBuffer(unknownPacket.data);
final String channel = PacketTypes.readString(data, Short.MAX_VALUE); // channel
final ByteBuf newData = this.handleC2P(unknownPacket, channel, data, listeners);
if (newData == data) {
return true;
} else if (newData != null) {
final ByteBuf newCustomPayloadData = Unpooled.buffer();
PacketTypes.writeString(newCustomPayloadData, channel); // channel
newCustomPayloadData.writeBytes(newData);
unknownPacket.data = ByteBufUtil.getBytes(newCustomPayloadData);
return true;
} else {
return false;
}
}
return super.handleC2P(packet, listeners);
}
public ByteBuf handleC2P(final UnknownPacket packet, final String channel, final ByteBuf data, final List<ChannelFutureListener> listeners) throws Exception {
return data;
}
public ByteBuf handleP2S(final UnknownPacket packet, final String channel, final ByteBuf data, final List<ChannelFutureListener> listeners) throws Exception {
return data;
}
}

View File

@ -19,8 +19,10 @@ package net.raphimc.viaproxy.proxy.packethandler;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import net.lenni0451.mcstructs.text.components.StringComponent;
import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.packet.IPacket;
@ -28,40 +30,51 @@ import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.UnknownPacket;
import net.raphimc.netminecraft.packet.impl.login.C2SLoginCustomPayloadPacket;
import net.raphimc.netminecraft.packet.impl.login.C2SLoginKeyPacket1_7;
import net.raphimc.netminecraft.packet.impl.login.S2CLoginCustomPayloadPacket;
import net.raphimc.netminecraft.packet.impl.login.S2CLoginDisconnectPacket1_20_3;
import net.raphimc.viaproxy.proxy.external_interface.OpenAuthModConstants;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class OpenAuthModPacketHandler extends PacketHandler {
private final int customPayloadId;
private final int c2sCustomPayloadId;
private final int s2cCustomPayloadId;
private final AtomicInteger id = new AtomicInteger(0);
private final Map<Integer, CompletableFuture<ByteBuf>> customPayloadListener = new ConcurrentHashMap<>();
public OpenAuthModPacketHandler(ProxyConnection proxyConnection) {
super(proxyConnection);
this.customPayloadId = MCPackets.C2S_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion());
this.c2sCustomPayloadId = MCPackets.C2S_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion());
this.s2cCustomPayloadId = MCPackets.S2C_PLUGIN_MESSAGE.getId(proxyConnection.getClientVersion().getVersion());
}
@Override
public boolean handleC2P(IPacket packet, List<ChannelFutureListener> listeners) {
if (packet instanceof UnknownPacket unknownPacket && this.proxyConnection.getC2pConnectionState() == ConnectionState.PLAY) {
if (unknownPacket.packetId == this.customPayloadId) {
if (unknownPacket.packetId == this.c2sCustomPayloadId) {
final ByteBuf data = Unpooled.wrappedBuffer(unknownPacket.data);
final String channel = PacketTypes.readString(data, Short.MAX_VALUE); // channel
if (channel.equals(OpenAuthModConstants.DATA_CHANNEL) && this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(data), data)) {
if (channel.equals(OpenAuthModConstants.DATA_CHANNEL) && this.handleCustomPayload(PacketTypes.readVarInt(data), data)) {
return false;
}
}
} else if (packet instanceof C2SLoginCustomPayloadPacket loginCustomPayload) {
if (loginCustomPayload.response != null && this.proxyConnection.handleCustomPayload(loginCustomPayload.queryId, Unpooled.wrappedBuffer(loginCustomPayload.response))) {
if (loginCustomPayload.response != null && this.handleCustomPayload(loginCustomPayload.queryId, Unpooled.wrappedBuffer(loginCustomPayload.response))) {
return false;
}
} else if (packet instanceof C2SLoginKeyPacket1_7 loginKeyPacket) {
if (this.proxyConnection.getClientVersion().olderThanOrEqualTo(ProtocolVersion.v1_12_2) && new String(loginKeyPacket.encryptedNonce, StandardCharsets.UTF_8).equals(OpenAuthModConstants.DATA_CHANNEL)) { // 1.8-1.12.2 OpenAuthMod response handling
final ByteBuf byteBuf = Unpooled.wrappedBuffer(loginKeyPacket.encryptedSecretKey);
this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(byteBuf), byteBuf);
this.handleCustomPayload(PacketTypes.readVarInt(byteBuf), byteBuf);
return false;
}
}
@ -69,4 +82,45 @@ public class OpenAuthModPacketHandler extends PacketHandler {
return true;
}
public CompletableFuture<ByteBuf> sendCustomPayload(final String channel, final ByteBuf data) {
if (channel.length() > 20) throw new IllegalStateException("Channel name can't be longer than 20 characters");
final CompletableFuture<ByteBuf> future = new CompletableFuture<>();
final int id = this.id.getAndIncrement();
switch (this.proxyConnection.getC2pConnectionState()) {
case LOGIN:
if (this.proxyConnection.getClientVersion().newerThanOrEqualTo(ProtocolVersion.v1_13)) {
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginCustomPayloadPacket(id, channel, PacketTypes.readReadableBytes(data))).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
} else {
final ByteBuf disconnectPacketData = Unpooled.buffer();
PacketTypes.writeString(disconnectPacketData, channel);
PacketTypes.writeVarInt(disconnectPacketData, id);
disconnectPacketData.writeBytes(data);
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginDisconnectPacket1_20_3(new StringComponent("§cYou need to install OpenAuthMod in order to join this server.§k\n" + Base64.getEncoder().encodeToString(ByteBufUtil.getBytes(disconnectPacketData)) + "\n" + OpenAuthModConstants.LEGACY_MAGIC_STRING))).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
break;
case PLAY:
final ByteBuf customPayloadPacket = Unpooled.buffer();
PacketTypes.writeVarInt(customPayloadPacket, this.s2cCustomPayloadId);
PacketTypes.writeString(customPayloadPacket, channel); // channel
PacketTypes.writeVarInt(customPayloadPacket, id);
customPayloadPacket.writeBytes(data);
this.proxyConnection.getC2P().writeAndFlush(customPayloadPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
break;
default:
throw new IllegalStateException("Can't send a custom payload packet during " + this.proxyConnection.getC2pConnectionState());
}
this.customPayloadListener.put(id, future);
return future;
}
private boolean handleCustomPayload(final int id, final ByteBuf data) {
if (this.customPayloadListener.containsKey(id)) {
this.customPayloadListener.remove(id).complete(data);
return true;
}
return false;
}
}

View File

@ -0,0 +1,75 @@
/*
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
* Copyright (C) 2021-2024 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 <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viaproxy.proxy.packethandler;
import com.viaversion.viaversion.util.Key;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.UnknownPacket;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import net.raphimc.viaproxy.util.logging.Logger;
import java.net.InetSocketAddress;
import java.util.List;
public class SimpleVoiceChatPacketHandler extends CustomPayloadPacketHandler {
private static final String SECRET_CHANNEL = "voicechat:secret";
public SimpleVoiceChatPacketHandler(ProxyConnection proxyConnection) {
super(proxyConnection);
}
@Override
public ByteBuf handleP2S(UnknownPacket packet, String channel, ByteBuf data, List<ChannelFutureListener> listeners) throws Exception {
if (Key.namespaced(channel).equals(SECRET_CHANNEL)) {
try {
final ByteBuf newData = Unpooled.buffer();
PacketTypes.writeUuid(newData, PacketTypes.readUuid(data)); // secret
final int port = data.readInt(); // port
newData.writeInt(port);
PacketTypes.writeUuid(newData, PacketTypes.readUuid(data)); // player uuid
newData.writeByte(data.readByte()); // codec
newData.writeInt(data.readInt()); // mtu size
newData.writeDouble(data.readDouble()); // voice chat distance
newData.writeInt(data.readInt()); // keep alive
newData.writeBoolean(data.readBoolean()); // groups enabled
final String voiceHost = PacketTypes.readString(data, Short.MAX_VALUE); // voice host
if (voiceHost.isEmpty()) {
if (this.proxyConnection.getServerAddress() instanceof InetSocketAddress serverAddress) {
PacketTypes.writeString(newData, new InetSocketAddress(serverAddress.getAddress(), port).toString());
} else {
throw new IllegalArgumentException("Server address must be an InetSocketAddress");
}
} else {
PacketTypes.writeString(newData, voiceHost);
}
newData.writeBytes(data);
return newData;
} catch (Throwable e) {
Logger.LOGGER.error("Failed to handle simple voice chat packet", e);
return super.handleP2S(packet, channel, data, listeners);
}
}
return super.handleP2S(packet, channel, data, listeners);
}
}

View File

@ -17,9 +17,13 @@
*/
package net.raphimc.viaproxy.proxy.packethandler;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.netty.channel.ChannelFutureListener;
import net.raphimc.netminecraft.packet.IPacket;
import net.raphimc.netminecraft.packet.impl.status.S2CStatusPongPacket;
import net.raphimc.netminecraft.packet.impl.status.S2CStatusResponsePacket;
import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import java.util.List;
@ -34,6 +38,13 @@ public class StatusPacketHandler extends PacketHandler {
public boolean handleP2S(IPacket packet, List<ChannelFutureListener> listeners) {
if (packet instanceof S2CStatusPongPacket) {
listeners.add(ChannelFutureListener.CLOSE);
} else if (packet instanceof S2CStatusResponsePacket statusResponsePacket && !ViaProxy.getConfig().getCustomMotd().isBlank()) {
try {
final JsonObject obj = JsonParser.parseString(statusResponsePacket.statusJson).getAsJsonObject();
obj.addProperty("description", ViaProxy.getConfig().getCustomMotd());
statusResponsePacket.statusJson = obj.toString();
} catch (Throwable ignored) {
}
}
return true;

View File

@ -21,7 +21,6 @@ import com.mojang.authlib.GameProfile;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import net.raphimc.netminecraft.constants.ConnectionState;
@ -30,7 +29,6 @@ import net.raphimc.netminecraft.util.ChannelType;
import java.net.SocketAddress;
import java.security.Key;
import java.util.concurrent.CompletableFuture;
public class DummyProxyConnection extends ProxyConnection {
@ -118,16 +116,6 @@ public class DummyProxyConnection extends ProxyConnection {
throw new UnsupportedOperationException();
}
@Override
public CompletableFuture<ByteBuf> sendCustomPayload(String channel, ByteBuf data) {
throw new UnsupportedOperationException();
}
@Override
public boolean handleCustomPayload(int id, ByteBuf data) {
throw new UnsupportedOperationException();
}
@Override
public boolean isClosed() {
return false;

View File

@ -24,7 +24,6 @@ import com.viaversion.viaversion.libs.gson.JsonObject;
import com.viaversion.viaversion.libs.gson.JsonPrimitive;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.AttributeKey;
@ -39,13 +38,11 @@ import net.raphimc.netminecraft.netty.connection.NetClient;
import net.raphimc.netminecraft.netty.crypto.AESEncryption;
import net.raphimc.netminecraft.packet.PacketTypes;
import net.raphimc.netminecraft.packet.impl.login.C2SLoginHelloPacket1_7;
import net.raphimc.netminecraft.packet.impl.login.S2CLoginCustomPayloadPacket;
import net.raphimc.netminecraft.packet.impl.login.S2CLoginDisconnectPacket1_20_3;
import net.raphimc.netminecraft.packet.impl.status.S2CStatusResponsePacket;
import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil;
import net.raphimc.netminecraft.util.ChannelType;
import net.raphimc.viaproxy.cli.ConsoleFormatter;
import net.raphimc.viaproxy.proxy.external_interface.OpenAuthModConstants;
import net.raphimc.viaproxy.proxy.packethandler.PacketHandler;
import net.raphimc.viaproxy.proxy.util.CloseAndReturn;
import net.raphimc.viaproxy.util.logging.Logger;
@ -54,12 +51,7 @@ import java.net.SocketAddress;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
@ -70,9 +62,6 @@ public class ProxyConnection extends NetClient {
private final Channel c2p;
private final List<PacketHandler> packetHandlers = new ArrayList<>();
private final AtomicInteger customPayloadId = new AtomicInteger(0);
private final Map<Integer, CompletableFuture<ByteBuf>> customPayloadListener = new ConcurrentHashMap<>();
private SocketAddress serverAddress;
private ProtocolVersion serverVersion;
@ -128,6 +117,15 @@ public class ProxyConnection extends NetClient {
return this.packetHandlers;
}
public <T> T getPacketHandler(final Class<T> packetHandlerType) {
for (final PacketHandler packetHandler : this.packetHandlers) {
if (packetHandlerType.isInstance(packetHandler)) {
return packetHandlerType.cast(packetHandler);
}
}
return null;
}
public SocketAddress getServerAddress() {
return this.serverAddress;
}
@ -239,47 +237,6 @@ public class ProxyConnection extends NetClient {
}
}
public CompletableFuture<ByteBuf> sendCustomPayload(final String channel, final ByteBuf data) {
if (channel.length() > 20) throw new IllegalStateException("Channel name can't be longer than 20 characters");
final CompletableFuture<ByteBuf> future = new CompletableFuture<>();
final int id = this.customPayloadId.getAndIncrement();
switch (this.c2pConnectionState) {
case LOGIN:
if (this.clientVersion.newerThanOrEqualTo(ProtocolVersion.v1_13)) {
this.c2p.writeAndFlush(new S2CLoginCustomPayloadPacket(id, channel, PacketTypes.readReadableBytes(data))).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
} else {
final ByteBuf disconnectPacketData = Unpooled.buffer();
PacketTypes.writeString(disconnectPacketData, channel);
PacketTypes.writeVarInt(disconnectPacketData, id);
disconnectPacketData.writeBytes(data);
this.c2p.writeAndFlush(new S2CLoginDisconnectPacket1_20_3(new StringComponent("§cYou need to install OpenAuthMod in order to join this server.§k\n" + Base64.getEncoder().encodeToString(ByteBufUtil.getBytes(disconnectPacketData)) + "\n" + OpenAuthModConstants.LEGACY_MAGIC_STRING))).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
break;
case PLAY:
final ByteBuf customPayloadPacket = Unpooled.buffer();
PacketTypes.writeVarInt(customPayloadPacket, MCPackets.S2C_PLUGIN_MESSAGE.getId(this.clientVersion.getVersion()));
PacketTypes.writeString(customPayloadPacket, channel); // channel
PacketTypes.writeVarInt(customPayloadPacket, id);
customPayloadPacket.writeBytes(data);
this.c2p.writeAndFlush(customPayloadPacket).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
break;
default:
throw new IllegalStateException("Can't send a custom payload packet during " + this.c2pConnectionState);
}
this.customPayloadListener.put(id, future);
return future;
}
public boolean handleCustomPayload(final int id, final ByteBuf data) {
if (this.customPayloadListener.containsKey(id)) {
this.customPayloadListener.remove(id).complete(data);
return true;
}
return false;
}
public void kickClient(final String message) throws CloseAndReturn {
Logger.u_err("proxy kick", this.c2p.remoteAddress(), this.getGameProfile(), ConsoleFormatter.convert(message));

View File

@ -49,6 +49,7 @@ public class AdvancedTab extends UITab {
JCheckBox chatSigning;
JCheckBox ignorePacketTranslationErrors;
JCheckBox allowBetaPinging;
JCheckBox simpleVoiceChatSupport;
JButton viaVersionDumpButton;
JButton uploadLogsButton;
@ -68,6 +69,9 @@ public class AdvancedTab extends UITab {
JPanel body = new JPanel();
body.setLayout(new GridBagLayout());
JPanel checkboxes = new JPanel();
checkboxes.setLayout(new GridLayout(0, 2, BORDER_PADDING, BORDER_PADDING));
int gridy = 0;
{
JLabel bindPortLabel = new JLabel(I18n.get("tab.advanced.bind_address.label"));
@ -94,34 +98,42 @@ public class AdvancedTab extends UITab {
this.proxyOnlineMode = new JCheckBox(I18n.get("tab.advanced.proxy_online_mode.label"));
this.proxyOnlineMode.setToolTipText(I18n.get("tab.advanced.proxy_online_mode.tooltip"));
this.proxyOnlineMode.setSelected(ViaProxy.getConfig().isProxyOnlineMode());
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, BORDER_PADDING, 0, 0).anchor(GBC.NORTHWEST).add(this.proxyOnlineMode);
checkboxes.add(this.proxyOnlineMode);
}
{
this.legacySkinLoading = new JCheckBox(I18n.get("tab.advanced.legacy_skin_loading.label"));
this.legacySkinLoading.setToolTipText(I18n.get("tab.advanced.legacy_skin_loading.tooltip"));
ViaProxy.getSaveManager().uiSave.loadCheckBox("legacy_skin_loading", this.legacySkinLoading);
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, BORDER_PADDING, 0, 0).anchor(GBC.NORTHWEST).add(this.legacySkinLoading);
checkboxes.add(this.legacySkinLoading);
}
{
this.chatSigning = new JCheckBox(I18n.get("tab.advanced.chat_signing.label"));
this.chatSigning.setToolTipText(I18n.get("tab.advanced.chat_signing.tooltip"));
this.chatSigning.setSelected(ViaProxy.getConfig().shouldSignChat());
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, BORDER_PADDING, 0, 0).anchor(GBC.NORTHWEST).add(this.chatSigning);
checkboxes.add(this.chatSigning);
}
{
this.ignorePacketTranslationErrors = new JCheckBox(I18n.get("tab.advanced.ignore_packet_translation_errors.label"));
this.ignorePacketTranslationErrors.setToolTipText(I18n.get("tab.advanced.ignore_packet_translation_errors.tooltip"));
this.ignorePacketTranslationErrors.setSelected(false);
this.ignorePacketTranslationErrors.setSelected(ViaProxy.getConfig().shouldIgnoreProtocolTranslationErrors());
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, BORDER_PADDING, 0, 0).anchor(GBC.NORTHWEST).add(this.ignorePacketTranslationErrors);
checkboxes.add(this.ignorePacketTranslationErrors);
}
{
this.allowBetaPinging = new JCheckBox(I18n.get("tab.advanced.allow_beta_pinging.label"));
this.allowBetaPinging.setToolTipText(I18n.get("tab.advanced.allow_beta_pinging.tooltip"));
this.allowBetaPinging.setSelected(false);
this.allowBetaPinging.setSelected(ViaProxy.getConfig().shouldAllowBetaPinging());
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, BORDER_PADDING, 0, 0).anchor(GBC.NORTHWEST).add(this.allowBetaPinging);
checkboxes.add(this.allowBetaPinging);
}
{
this.simpleVoiceChatSupport = new JCheckBox(I18n.get("tab.advanced.simple_voice_chat_support.label"));
this.simpleVoiceChatSupport.setToolTipText(I18n.get("tab.advanced.simple_voice_chat_support.tooltip"));
this.simpleVoiceChatSupport.setSelected(false);
this.simpleVoiceChatSupport.setSelected(ViaProxy.getConfig().shouldSupportSimpleVoiceChat());
checkboxes.add(this.simpleVoiceChatSupport);
}
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, BORDER_PADDING, 0, BODY_BLOCK_PADDING).fill(GBC.BOTH).weight(1, 1).add(checkboxes);
parent.add(body, BorderLayout.NORTH);
}
@ -197,6 +209,7 @@ public class AdvancedTab extends UITab {
ViaProxy.getConfig().setChatSigning(this.chatSigning.isSelected());
ViaProxy.getConfig().setIgnoreProtocolTranslationErrors(this.ignorePacketTranslationErrors.isSelected());
ViaProxy.getConfig().setAllowBetaPinging(this.allowBetaPinging.isSelected());
ViaProxy.getConfig().setSimpleVoiceChatSupport(this.simpleVoiceChatSupport.isSelected());
}
}

View File

@ -56,6 +56,8 @@ tab.advanced.ignore_packet_translation_errors.label=Datenpaket Übersetzungsfehl
tab.advanced.ignore_packet_translation_errors.tooltip=Wenn du diese Option aktivierst, wirst du nicht mehr vom Server gekickt, wenn es einen Datenpaket Übersetzungsfehler gibt, sondern der Fehler wird nur in der Konsole angezeigt.\nDiese Option könnte abhängig vom Datenpaket, welches nicht übersetzt werden konnte, Folgeprobleme verursachen.
tab.advanced.allow_beta_pinging.label=Aktiviere Pings für <= b1.7.3
tab.advanced.allow_beta_pinging.tooltip=Aktiviert das Pingen für <= b1.7.3 Server. Das könnte zu Problemen bei Servern, welche zu schnelle Verbindungen blockieren führen.
tab.advanced.simple_voice_chat_support.label=Simple Voice Chat Unterstützung
tab.advanced.simple_voice_chat_support.tooltip=Aktiviert lesen und ändern der Simple Voice Chat Mod Pakete.
tab.advanced.create_viaversion_dump.label=ViaVersion Dump erstellen
tab.advanced.create_viaversion_dump.success=Der ViaVersion Dump Link wurde in die Zwischenablage kopiert.
tab.advanced.upload_latest_log.label=Neueste Log-Datei hochladen

View File

@ -56,6 +56,8 @@ tab.advanced.ignore_packet_translation_errors.label=Ignore packet translation er
tab.advanced.ignore_packet_translation_errors.tooltip=Enabling this will prevent getting disconnected from the server when a packet translation error occurs and instead only print the error in the console.\nThis may cause issues depending on the type of packet which failed to translate.
tab.advanced.allow_beta_pinging.label=Allow <= b1.7.3 pinging
tab.advanced.allow_beta_pinging.tooltip=Enabling this will allow you to ping <= b1.7.3 servers. This may cause issues with servers that block too frequent connections.
tab.advanced.simple_voice_chat_support.label=Simple Voice Chat Support
tab.advanced.simple_voice_chat_support.tooltip=Enables handling and rewriting of Simple Voice Chat mod packets.
tab.advanced.create_viaversion_dump.label=Create ViaVersion dump
tab.advanced.create_viaversion_dump.success=Copied ViaVersion dump link to clipboard.
tab.advanced.upload_latest_log.label=Upload latest log file

View File

@ -55,6 +55,9 @@ ignore-protocol-translation-errors: false
# Allow <= 1.6.4 clients to connect through ViaProxy to the target server. (No protocol translation or packet handling)
allow-legacy-client-passthrough: false
#
# Custom MOTD to send when clients ping the proxy. Leave empty to use the target server's MOTD.
custom-motd: ""
#
# 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
resource-pack-url: ""
@ -65,5 +68,8 @@ resource-pack-url: ""
# internal: Internal wildcard domain handling. Intended for local usage by custom clients. (Example: address:port\7version\7classic-mppass)
wildcard-domain-handling: "none"
#
# Enables handling and rewriting of Simple Voice Chat mod packets.
simple-voice-chat-support: false
#
# Configuration version. Do not change this.
config-version: 1