Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions common-proxy/src/main/java/de/maxhenkel/voicechat/VoiceProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

public abstract class VoiceProxy {
Expand Down Expand Up @@ -255,4 +258,109 @@ public VoiceProxySniffer getSniffer() {
return voiceProxySniffer;
}

/**
* Returns a configured voice host override for the backend server the given player is connected to.
* Empty string means no override.
*/
public String getPerServerVoiceHost(UUID playerUUID) {
Map<String, HostPort> map = parseServerVoiceHosts();
if (map.isEmpty()) {
return "";
}
String serverName = resolvePlayersBackendServerName(playerUUID);
if (serverName == null) {
return "";
}
HostPort hp = map.get(serverName.toLowerCase(Locale.ROOT));
if (hp == null) {
return "";
}
return hp.host;
}

/**
* Returns an optional port override for the backend server the given player is connected to.
* Null means no override; use the sniffed backend voice port.
*/
public Integer getPerServerVoicePortOverride(UUID playerUUID) {
Map<String, HostPort> map = parseServerVoiceHosts();
if (map.isEmpty()) {
return null;
}
String serverName = resolvePlayersBackendServerName(playerUUID);
if (serverName == null) {
return null;
}
HostPort hp = map.get(serverName.toLowerCase(Locale.ROOT));
if (hp == null) {
return null;
}
return hp.portOverride;
}

private String resolvePlayersBackendServerName(UUID playerUUID) {
try {
InetSocketAddress backend = getDefaultBackendSocket(playerUUID);
if (backend == null) {
return null;
}
String host = backend.getHostString();
int port = backend.getPort();
for (BackendServer s : getBackendServers()) {
if (s.getAddress() instanceof InetSocketAddress) {
InetSocketAddress isa = (InetSocketAddress) s.getAddress();
if (isa.getHostString().equalsIgnoreCase(host) && isa.getPort() == port) {
return s.getName();
}
}
}
} catch (Exception ignored) {
}
return null;
}

private Map<String, HostPort> parseServerVoiceHosts() {
String raw = voiceProxyConfig.serverVoiceHosts.get();
Map<String, HostPort> out = new HashMap<>();
if (raw == null || raw.trim().isEmpty()) {
return out;
}
String[] entries = raw.split(",");
for (String e : entries) {
String entry = e.trim();
if (entry.isEmpty()) continue;
int eq = entry.indexOf('=');
if (eq <= 0 || eq >= entry.length() - 1) continue;
String name = entry.substring(0, eq).trim();
String hostPort = entry.substring(eq + 1).trim();
if (name.isEmpty() || hostPort.isEmpty()) continue;
String host = hostPort;
Integer port = null;
int lastColon = hostPort.lastIndexOf(':');
if (lastColon > 0 && lastColon < hostPort.length() - 1 && hostPort.indexOf(']') == -1) {
// Simple host:port parsing; IPv6 with brackets not supported in this shorthand
String pstr = hostPort.substring(lastColon + 1);
try {
int p = Integer.parseInt(pstr);
if (p > 0 && p <= 65535) {
port = p;
host = hostPort.substring(0, lastColon);
}
} catch (NumberFormatException ignored) {
}
}
out.put(name.toLowerCase(Locale.ROOT), new HostPort(host, port));
}
return out;
}

private static class HostPort {
final String host;
final Integer portOverride;
HostPort(String host, Integer portOverride) {
this.host = host;
this.portOverride = portOverride;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class ProxyConfig {
public ConfigEntry<String> bindAddress;
public ConfigEntry<String> voiceHost;
public ConfigEntry<Boolean> allowPings;
public ConfigEntry<String> serverVoiceHosts;

public ProxyConfig(ConfigBuilder builder) {
builder.header(String.format("Simple Voice Chat proxy config v%s", BuildConstants.MOD_VERSION));
Expand Down Expand Up @@ -40,6 +41,13 @@ public ProxyConfig(ConfigBuilder builder) {
.booleanEntry("allow_pings", true,
"If the voice chat proxy server should reply to external pings"
);

serverVoiceHosts = builder
.stringEntry("server_voice_hosts", "",
"Optional per-server voice host overrides (comma-separated)",
"Format: serverName=host[:port],serverName2=host2[:port2]",
"If port is omitted, the backend voice port sniffed from the server will be used"
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,25 @@ public UUID getPlayerUUID() {
}

/**
* Modifies the packet to use the proxy port and clears the voice host.
* If a voice host is present on the proxy, this value will be used.
* Modifies the packet based on proxy configuration.
* If a per-server override exists for the players backend server, use that host
* and optionally a custom port, otherwise force clients to connect to the proxy port/host.
*
* @param voiceProxy the proxy
* @param proxyPlayerUUID the UUID of the player on the proxy
* @return the modified packet
*/
public ByteBuffer patch(VoiceProxy voiceProxy) {
public ByteBuffer patch(VoiceProxy voiceProxy, UUID proxyPlayerUUID) {
String overrideHost = voiceProxy.getPerServerVoiceHost(proxyPlayerUUID);
if (overrideHost != null && !overrideHost.isEmpty()) {
voiceHost = overrideHost;
Integer overridePort = voiceProxy.getPerServerVoicePortOverride(proxyPlayerUUID);
if (overridePort != null) {
serverPort = overridePort;
}
return toBytes();
}

serverPort = voiceProxy.getPort();
voiceHost = voiceProxy.getConfig().voiceHost.get();
return toBytes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private ByteBuffer handleSecretPacket(ByteBuffer message, UUID playerUUID) throw
SniffedSecretPacket packet = SniffedSecretPacket.fromBytes(message, compatibilityVersion);
playerUUIDMap.put(packet.getPlayerUUID(), playerUUID);
serverUDPPortMap.put(playerUUID, packet.getServerPort());
return packet.patch(voiceProxy);
return packet.patch(voiceProxy, playerUUID);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions velocity/changelog.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
- Added ping command

- Velocity: Added per-server voice host overrides via `voicechat-proxy.properties` key `server_voice_hosts`.
Example: `server_voice_hosts=main=voice.example.com:24454,nether=voice-nether.example.com:25500`