Fix language and update group members on 2.5 clients
Build AsminVoicechat / build-and-release (push) Successful in 2m24s
Build AsminVoicechat / build-and-release (push) Successful in 2m24s
This commit is contained in:
1 parent
71396a86a0
commit
f5429ee4c6
13 files changed
+93
-16
No files matched your search
@@ -33,7 +33,7 @@ jobs:
|
||||
run: |
|
||||
latest="$(git tag --list 'v[0-9]*' --sort=-version:refname | head -n 1)"
|
||||
if [[ -z "$latest" ]]; then
|
||||
version="1.0.0"
|
||||
version="1.0.3"
|
||||
else
|
||||
current="${latest#v}"
|
||||
IFS='.' read -r major minor patch <<< "$current"
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
build/
|
||||
*.class
|
||||
*.log
|
||||
/LOCAL_AI_NOTES.md
|
||||
AsminVoiceChat-*.jar
|
||||
Binary file not shown.
@@ -10,8 +10,12 @@ The plugin is compiled to Java 8 bytecode and runs on newer JVMs.
|
||||
The server negotiates the protocol generation during the existing voice-chat
|
||||
handshake and stores it with that player's 16-byte secret. The UDP path then
|
||||
uses AES-CBC for 2.5 clients and AES-GCM for 2.6 clients. Shared packet layouts
|
||||
are passed through the existing 2.6 voice routing. The fork changes only the
|
||||
per-client cipher selection and compatibility-version acceptance.
|
||||
are passed through the existing 2.6 voice routing. State messages are sent on
|
||||
the channel names expected by each client generation, so 2.5 clients receive
|
||||
their player and group-member updates as well. For compatibility 18 the server
|
||||
maps `voicechat:state` and `voicechat:states` to the older
|
||||
`voicechat:player_state` and `voicechat:player_states` channels. When a player
|
||||
leaves, it sends the disconnected state because 2.5 has no remove-state packet.
|
||||
|
||||
The Bukkit plugin source and API source are in `source/bukkit/` and
|
||||
`source/api/`. The cipher and compatibility changes are in the regular source
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = 'de.maxhenkel.voicechat'
|
||||
version = findProperty('buildVersion') ?: '1.0.0'
|
||||
version = findProperty('buildVersion') ?: '1.0.3'
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name: AsminVoicechat
|
||||
version: 1.0.0
|
||||
version: 1.0.3
|
||||
main: de.maxhenkel.voicechat.Voicechat
|
||||
api-version: '1.13'
|
||||
prefix: AsminVoicechat
|
||||
|
||||
@@ -112,6 +112,8 @@ public class VoiceChatCommands implements CommandExecutor, TabCompleter {
|
||||
return true;
|
||||
}
|
||||
|
||||
int clientCompatibilityVersion = Voicechat.SERVER.getClientCompatibilityVersion(player.getUniqueId());
|
||||
|
||||
ClientConnection clientConnection = Voicechat.SERVER.getServer().getConnections().get(player.getUniqueId());
|
||||
|
||||
if (clientConnection == null) {
|
||||
@@ -130,6 +132,8 @@ public class VoiceChatCommands implements CommandExecutor, TabCompleter {
|
||||
} else {
|
||||
NetManager.sendMessage(commandSender, "message.voicechat.ping_received_attempt", String.valueOf(pingMilliseconds), String.valueOf(attempts));
|
||||
}
|
||||
commandSender.sendMessage("Voice chat connection verified (client compatibility version "
|
||||
+ clientCompatibilityVersion + ").");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -326,4 +330,4 @@ public class VoiceChatCommands implements CommandExecutor, TabCompleter {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package de.maxhenkel.voicechat.config;
|
||||
|
||||
import de.maxhenkel.configbuilder.ConfigBuilder;
|
||||
import de.maxhenkel.configbuilder.entry.ConfigEntry;
|
||||
import de.maxhenkel.voicechat.BuildConstants;
|
||||
import de.maxhenkel.voicechat.Voicechat;
|
||||
import de.maxhenkel.voicechat.voice.common.AudioUtils;
|
||||
|
||||
@@ -28,7 +27,7 @@ public class ServerConfig {
|
||||
public ConfigEntry<Boolean> threadedServerSupport;
|
||||
|
||||
public ServerConfig(ConfigBuilder builder) {
|
||||
builder.header(String.format("%s server config v%s", BuildConstants.PLUGIN_NAME, Voicechat.INSTANCE.getDescription().getVersion()));
|
||||
builder.header("AsminVoiceChat (Simple Voice Chat) server config v1.0.2");
|
||||
|
||||
voiceChatPort = builder
|
||||
.integerEntry("port", 24454, -1, 65535,
|
||||
@@ -126,4 +125,4 @@ public class ServerConfig {
|
||||
VOIP, AUDIO, RESTRICTED_LOWDELAY;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,9 @@ public class NetManager implements Listener {
|
||||
registerOutgoingPacket(JoinedGroupPacket.class);
|
||||
registerOutgoingPacket(AddCategoryPacket.class);
|
||||
registerOutgoingPacket(RemoveCategoryPacket.class);
|
||||
|
||||
registerOutgoingChannel(ProtocolChannelCompatibility.LEGACY_PLAYER_STATE);
|
||||
registerOutgoingChannel(ProtocolChannelCompatibility.LEGACY_PLAYER_STATES);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to register voice chat packets", e);
|
||||
}
|
||||
@@ -100,6 +103,10 @@ public class NetManager implements Listener {
|
||||
|
||||
private <T extends Packet<?>> void registerOutgoingPacket(Class<T> packetClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||
String id = packetClass.getDeclaredConstructor().newInstance().getID().toString();
|
||||
registerOutgoingChannel(id);
|
||||
}
|
||||
|
||||
private void registerOutgoingChannel(String id) {
|
||||
packets.add(id);
|
||||
Bukkit.getMessenger().registerOutgoingPluginChannel(Voicechat.INSTANCE, id);
|
||||
}
|
||||
@@ -114,7 +121,11 @@ public class NetManager implements Listener {
|
||||
p.toBytes(buf);
|
||||
byte[] bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
player.sendPluginMessage(Voicechat.INSTANCE, p.getID().toString(), bytes);
|
||||
int compatibilityVersion = Voicechat.SERVER.getClientCompatibilityVersion(player.getUniqueId());
|
||||
String channel = ProtocolChannelCompatibility.outgoingChannel(p.getID().toString(), compatibilityVersion);
|
||||
if (channel != null) {
|
||||
player.sendPluginMessage(Voicechat.INSTANCE, channel, bytes);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Voicechat.LOGGER.error("Failed to send packet to '{}'", player.getName(), e);
|
||||
}
|
||||
@@ -129,4 +140,4 @@ public class NetManager implements Listener {
|
||||
Voicechat.compatibility.sendStatusMessage(player, key, args);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package de.maxhenkel.voicechat.net;
|
||||
|
||||
public final class ProtocolChannelCompatibility {
|
||||
public static final int LEGACY_COMPATIBILITY_VERSION = 18;
|
||||
|
||||
public static final String LEGACY_PLAYER_STATE = "voicechat:player_state";
|
||||
public static final String LEGACY_PLAYER_STATES = "voicechat:player_states";
|
||||
|
||||
private ProtocolChannelCompatibility() {
|
||||
}
|
||||
|
||||
public static String outgoingChannel(String channel, int compatibilityVersion) {
|
||||
if (compatibilityVersion != LEGACY_COMPATIBILITY_VERSION) {
|
||||
return channel;
|
||||
}
|
||||
switch (channel) {
|
||||
case "voicechat:state":
|
||||
return LEGACY_PLAYER_STATE;
|
||||
case "voicechat:states":
|
||||
return LEGACY_PLAYER_STATES;
|
||||
case "voicechat:remove_state":
|
||||
return null;
|
||||
default:
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-4
@@ -56,8 +56,7 @@ public class PlayerStateManager implements Listener {
|
||||
}
|
||||
|
||||
public void onPlayerHide(PlayerHideEvent event) {
|
||||
RemovePlayerStatePacket packet = new RemovePlayerStatePacket(event.getHiddenPlayer().getUniqueId());
|
||||
NetManager.sendToClient(event.getObserver(), packet);
|
||||
sendRemoveState(event.getObserver(), event.getHiddenPlayer());
|
||||
Voicechat.LOGGER.debug("Removing state of {} for {}", event.getHiddenPlayer().getName(), event.getObserver().getName());
|
||||
}
|
||||
|
||||
@@ -83,14 +82,22 @@ public class PlayerStateManager implements Listener {
|
||||
}
|
||||
|
||||
public void broadcastRemoveState(Player stateOwner) {
|
||||
RemovePlayerStatePacket packet = new RemovePlayerStatePacket(stateOwner.getUniqueId());
|
||||
for (Player receiver : Voicechat.INSTANCE.getServer().getOnlinePlayers()) {
|
||||
NetManager.sendToClient(receiver, packet);
|
||||
sendRemoveState(receiver, stateOwner);
|
||||
}
|
||||
// Send the default disconnected state to the API when disconnecting
|
||||
PluginManager.instance().onPlayerStateChanged(defaultDisconnectedState(stateOwner));
|
||||
}
|
||||
|
||||
private void sendRemoveState(Player receiver, Player stateOwner) {
|
||||
if (Voicechat.SERVER.getClientCompatibilityVersion(receiver.getUniqueId())
|
||||
== de.maxhenkel.voicechat.net.ProtocolChannelCompatibility.LEGACY_COMPATIBILITY_VERSION) {
|
||||
NetManager.sendToClient(receiver, new PlayerStatePacket(defaultDisconnectedState(stateOwner)));
|
||||
return;
|
||||
}
|
||||
NetManager.sendToClient(receiver, new RemovePlayerStatePacket(stateOwner.getUniqueId()));
|
||||
}
|
||||
|
||||
public void onPlayerCompatibilityCheckSucceeded(Player player) {
|
||||
List<PlayerState> stateList = new ArrayList<>(states.size());
|
||||
for (PlayerState state : states.values()) {
|
||||
|
||||
@@ -83,6 +83,10 @@ public class ServerVoiceEvents implements Listener {
|
||||
return isSupportedCompatibilityVersion(clientCompatibilities.getOrDefault(playerUuid, -1));
|
||||
}
|
||||
|
||||
public int getClientCompatibilityVersion(UUID playerUuid) {
|
||||
return clientCompatibilities.getOrDefault(playerUuid, -1);
|
||||
}
|
||||
|
||||
public static void sendIncompatibleMessage(Player player, int clientCompatibilityVersion) {
|
||||
if (clientCompatibilityVersion <= 6) {
|
||||
player.sendMessage(String.format(Voicechat.TRANSLATIONS.voicechatNotCompatibleMessage.get(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import de.maxhenkel.voicechat.voice.common.Secret;
|
||||
import de.maxhenkel.voicechat.net.ProtocolChannelCompatibility;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
@@ -9,6 +10,8 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class ProtocolCryptoSelfTest {
|
||||
public static void main(String[] args) throws Exception {
|
||||
testStateChannelCompatibility();
|
||||
|
||||
byte[] key = new byte[16];
|
||||
for (int i = 0; i < key.length; i++) key[i] = (byte) (i * 13 + 7);
|
||||
UUID legacyKey = uuidFromBytes(key);
|
||||
@@ -34,7 +37,23 @@ public class ProtocolCryptoSelfTest {
|
||||
throw new AssertionError("Hybrid server AES-GCM round-trip failed");
|
||||
}
|
||||
}
|
||||
System.out.println("PASS: 100 CBC client/server interop cycles and 100 GCM round-trips");
|
||||
System.out.println("PASS: legacy state-channel mapping, 100 CBC interop cycles and 100 GCM round-trips");
|
||||
}
|
||||
|
||||
private static void testStateChannelCompatibility() {
|
||||
check("voicechat:player_state", ProtocolChannelCompatibility.outgoingChannel("voicechat:state", 18));
|
||||
check("voicechat:player_states", ProtocolChannelCompatibility.outgoingChannel("voicechat:states", 18));
|
||||
if (ProtocolChannelCompatibility.outgoingChannel("voicechat:remove_state", 18) != null) {
|
||||
throw new AssertionError("The 2.5 client has no remove-state packet");
|
||||
}
|
||||
check("voicechat:state", ProtocolChannelCompatibility.outgoingChannel("voicechat:state", 20));
|
||||
check("voicechat:states", ProtocolChannelCompatibility.outgoingChannel("voicechat:states", 20));
|
||||
}
|
||||
|
||||
private static void check(String expected, String actual) {
|
||||
if (!expected.equals(actual)) {
|
||||
throw new AssertionError("Expected channel " + expected + ", got " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
private static UUID uuidFromBytes(byte[] bytes) {
|
||||
|
||||
Reference in new issue
Block a user