Added support for Simple Voice Chat passthrough

Closes #155
This commit is contained in:
RaphiMC 2024-05-04 18:59:29 +02:00
parent f5c330168f
commit 044e1d65a1
No known key found for this signature in database
GPG Key ID: 0F6BB0657A03AC94
10 changed files with 240 additions and 35 deletions

View File

@ -67,6 +67,7 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
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);
@ -86,6 +87,7 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
private String customMotd = "";
private String resourcePackUrl = "";
private WildcardDomainHandling wildcardDomainHandling = WildcardDomainHandling.NONE;
private boolean simpleVoiceChatSupport = false;
public ViaProxyConfig(final File configFile) {
super(configFile);
@ -110,6 +112,7 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
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
@ -141,6 +144,7 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
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 {
@ -176,6 +180,7 @@ public class ViaProxyConfig extends Config implements com.viaversion.viaversion.
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) {
@ -374,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

@ -216,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_12_2) && clientVersion.newerThan(ProtocolVersion.v1_12_2)) {
this.proxyConnection.getPacketHandlers().add(new SimpleVoiceChatPacketHandler(this.proxyConnection));
}
if (clientVersion.newerThanOrEqualTo(ProtocolVersion.v1_8)) {
this.proxyConnection.getPacketHandlers().add(new BrandCustomPayloadPacketHandler(this.proxyConnection));
}

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

@ -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

@ -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

@ -128,6 +128,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;
}

View File

@ -49,6 +49,7 @@ public class AdvancedTab extends UITab {
JCheckBox chatSigning;
JCheckBox ignorePacketTranslationErrors;
JCheckBox allowBetaPinging;
JCheckBox simpleVoiceChatSupport;
JButton viaVersionDumpButton;
JButton uploadLogsButton;
@ -122,6 +123,14 @@ public class AdvancedTab extends UITab {
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);
}
gridy = 4;
{
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());
GBC.create(body).grid(0, gridy++).insets(BODY_BLOCK_PADDING, 0, 0, BORDER_PADDING).anchor(GBC.SOUTHEAST).add(this.simpleVoiceChatSupport);
}
parent.add(body, BorderLayout.NORTH);
}
@ -197,6 +206,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

@ -68,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