Skip to content

Commit 4aa4968

Browse files
committed
Cross server violation sync
1 parent 4a90748 commit 4aa4968

18 files changed

Lines changed: 877 additions & 14 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.wdsj.asw.common.sync;
2+
3+
import javax.crypto.Mac;
4+
import javax.crypto.spec.SecretKeySpec;
5+
import java.nio.charset.StandardCharsets;
6+
import java.security.MessageDigest;
7+
import java.security.SecureRandom;
8+
import java.util.HexFormat;
9+
10+
public final class VelocitySyncProtocol {
11+
public static final int VERSION = 1;
12+
public static final String PATH = "/asw";
13+
14+
public static final String TYPE_HELLO = "hello";
15+
public static final String TYPE_HELLO_OK = "hello-ok";
16+
public static final String TYPE_VL_INCREMENT = "vl-increment";
17+
public static final String TYPE_VL_SYNC = "vl-sync";
18+
public static final String TYPE_VL_QUERY = "vl-query";
19+
public static final String TYPE_VL_RESET_REQUEST = "vl-reset-request";
20+
public static final String TYPE_VL_RESET = "vl-reset";
21+
public static final String TYPE_VL_RESET_ALL = "vl-reset-all";
22+
public static final String TYPE_PING = "ping";
23+
public static final String TYPE_PONG = "pong";
24+
25+
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
26+
27+
private VelocitySyncProtocol() {
28+
}
29+
30+
public static String nonce() {
31+
byte[] bytes = new byte[16];
32+
SECURE_RANDOM.nextBytes(bytes);
33+
return HexFormat.of().formatHex(bytes);
34+
}
35+
36+
public static String signature(String secret, String serverId, String nonce, long timestamp) {
37+
try {
38+
Mac mac = Mac.getInstance("HmacSHA256");
39+
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
40+
byte[] digest = mac.doFinal((serverId + nonce + timestamp).getBytes(StandardCharsets.UTF_8));
41+
return HexFormat.of().formatHex(digest);
42+
} catch (Exception exception) {
43+
throw new IllegalStateException("Unable to calculate Velocity sync signature", exception);
44+
}
45+
}
46+
47+
public static boolean signatureMatches(String expected, String actual) {
48+
if (expected == null || actual == null) {
49+
return false;
50+
}
51+
return MessageDigest.isEqual(
52+
expected.getBytes(StandardCharsets.UTF_8),
53+
actual.getBytes(StandardCharsets.UTF_8)
54+
);
55+
}
56+
}

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ cloudVersion=2.0.0-beta.15
1111
langchain4jVersion=1.16.3
1212
jmhVersion=1.37
1313
guavaVersion=33.4.0-jre
14+
javaWebSocketVersion=1.5.7

paper/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ dependencies {
5252
implementation("com.github.Anon8281:UniversalScheduler:0.1.7")
5353
implementation("org.bstats:bstats-bukkit:3.2.1")
5454
implementation("de.exlll:configlib-yaml:4.8.1")
55+
implementation("org.java-websocket:Java-WebSocket:${property("javaWebSocketVersion")}")
5556
runtimeOnly("org.snakeyaml:snakeyaml-engine:2.7")
5657

5758
testImplementation("org.junit.jupiter:junit-jupiter:6.1.1")
@@ -138,6 +139,7 @@ tasks.named<ShadowJar>("shadowJar") {
138139
relocate("org.snakeyaml.engine.external", "io.wdsj.asw.bukkit.libs.snakeyaml.engine.external")
139140
relocate("org.incendo", "io.wdsj.asw.bukkit.libs.incendo")
140141
relocate("io.leangen.geantyref", "io.wdsj.asw.bukkit.libs.geantyref")
142+
relocate("org.java_websocket", "io.wdsj.asw.bukkit.libs.websocket")
141143

142144
exclude("org/slf4j/**")
143145
exclude("net/kyori/**")
@@ -151,6 +153,7 @@ tasks.named<ShadowJar>("shadowJar") {
151153
exclude(dependency("org.incendo:.*:.*"))
152154
exclude(dependency("de.exlll:.*:.*"))
153155
exclude(dependency("org.snakeyaml:snakeyaml-engine:.*"))
156+
exclude(dependency("org.java-websocket:Java-WebSocket:.*"))
154157
}
155158
}
156159

paper/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.wdsj.asw.bukkit.permission.cache.CachingPermTool;
2727
import io.wdsj.asw.bukkit.proxy.velocity.VelocityChannel;
2828
import io.wdsj.asw.bukkit.proxy.velocity.VelocityReceiver;
29+
import io.wdsj.asw.bukkit.proxy.velocity.sync.VelocitySyncClient;
2930
import io.wdsj.asw.bukkit.service.ListenerService;
3031
import io.wdsj.asw.bukkit.setting.PaperConfigurationService;
3132
import io.wdsj.asw.bukkit.setting.PluginMessages;
@@ -69,6 +70,7 @@ public final class AdvancedSensitiveWords extends JavaPlugin {
6970
private PaperConfigurationService configurationService;
7071
private volatile Updater.UpdateResult updateResult = Updater.UpdateResult.noUpdate();
7172
private AswCommandRegistrar commandRegistrar;
73+
private VelocitySyncClient velocitySyncClient;
7274
public static TaskScheduler getScheduler() {
7375
return scheduler;
7476
}
@@ -92,6 +94,10 @@ public LlmChatDetectionService getLlmChatDetectionService() {
9294
return listenerService.getLlmChatDetectionService();
9395
}
9496

97+
public VelocitySyncClient getVelocitySyncClient() {
98+
return velocitySyncClient;
99+
}
100+
95101
public static <T> T setting(SettingKey<T> key) {
96102
return instance.configurationService.get(key);
97103
}
@@ -125,6 +131,7 @@ public void onEnable() {
125131
commandRegistrar.register();
126132
setupMetrics();
127133
registerVelocityChannel();
134+
startVelocitySyncClient();
128135
registerPlaceholderExpansion();
129136
scheduleViolationResetTask();
130137
long endTime = System.currentTimeMillis();
@@ -173,6 +180,7 @@ public void doInitTasks() {
173180

174181
@Override
175182
public void onDisable() {
183+
stopVelocitySyncClient();
176184
listenerService.unregisterListeners();
177185
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
178186
getServer().getMessenger().unregisterIncomingPluginChannel(this);
@@ -201,6 +209,7 @@ public void reloadPluginConfiguration() {
201209
if (listenerService != null) {
202210
listenerService.reloadConfiguration();
203211
}
212+
restartVelocitySyncClient();
204213
}
205214

206215
private void setupMetrics() {
@@ -220,6 +229,26 @@ private void registerVelocityChannel() {
220229
getServer().getMessenger().registerIncomingPluginChannel(this, VelocityChannel.CHANNEL, new VelocityReceiver());
221230
}
222231

232+
private void startVelocitySyncClient() {
233+
stopVelocitySyncClient();
234+
if (!configurationService.get(PluginSettings.VELOCITY_SYNC_ENABLED)) {
235+
return;
236+
}
237+
velocitySyncClient = new VelocitySyncClient(this);
238+
velocitySyncClient.start();
239+
}
240+
241+
private void stopVelocitySyncClient() {
242+
if (velocitySyncClient != null) {
243+
velocitySyncClient.close();
244+
velocitySyncClient = null;
245+
}
246+
}
247+
248+
private void restartVelocitySyncClient() {
249+
startVelocitySyncClient();
250+
}
251+
223252
private void registerPlaceholderExpansion() {
224253
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") &&
225254
configurationService.get(PluginSettings.ENABLE_PLACEHOLDER)) {
@@ -230,7 +259,7 @@ private void registerPlaceholderExpansion() {
230259

231260
private void scheduleViolationResetTask() {
232261
long resetIntervalTicks = configurationService.get(PluginSettings.VIOLATION_RESET_TIME) * 20L * 60L;
233-
violationResetTask = new ViolationResetTask().runTaskTimerAsynchronously(this, resetIntervalTicks, resetIntervalTicks);
262+
violationResetTask = new ViolationResetTask(configurationService).runTaskTimerAsynchronously(this, resetIntervalTicks, resetIntervalTicks);
234263
}
235264

236265
private void checkForUpdatesAsync() {

paper/src/main/java/io/wdsj/asw/bukkit/command/AswCommandService.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,14 @@ public void showPlayerInfo(CommandSender sender, Player player) {
163163
}
164164

165165
public void resetPlayerViolations(CommandSender sender, Player player, ModuleType moduleType) {
166-
if (moduleType == null) {
167-
ViolationCounter.INSTANCE.resetViolationCount(player);
168-
} else {
169-
ViolationCounter.INSTANCE.resetViolationCount(player, moduleType);
166+
boolean handledByProxy = plugin.getVelocitySyncClient() != null
167+
&& plugin.getVelocitySyncClient().requestReset(player, moduleType);
168+
if (!handledByProxy) {
169+
if (moduleType == null) {
170+
ViolationCounter.INSTANCE.resetViolationCount(player);
171+
} else {
172+
ViolationCounter.INSTANCE.resetViolationCount(player, moduleType);
173+
}
170174
}
171175
String message = MessageUtils.retrieveMessage(PluginMessages.MESSAGE_ON_COMMAND_RESET)
172176
.replace("%player%", player.getName())

paper/src/main/java/io/wdsj/asw/bukkit/manage/punish/PunishmentService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private static void executeConsoleCommand(Player player, String[] parts) {
124124

125125
private void executeProxyCommand(Player player, String[] parts) {
126126
requireArgument(parts, 1);
127-
if (configuration.get(PluginSettings.HOOK_VELOCITY)) {
127+
if (configuration.get(PluginSettings.ENABLE_ACTION_FORWARDING)) {
128128
VelocitySender.executeVelocityCommand(player, replacePlayerPlaceholder(parts[1], player));
129129
}
130130
}

paper/src/main/java/io/wdsj/asw/bukkit/manage/punish/ViolationCounter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.bukkit.entity.Player;
55

66
import java.util.Map;
7+
import java.util.EnumMap;
78
import java.util.UUID;
89
import java.util.concurrent.ConcurrentHashMap;
910

@@ -70,6 +71,32 @@ public void resetAllViolations() {
7071
violationCountMap.clear();
7172
}
7273

74+
public void setViolationCount(UUID playerId, ModuleType moduleType, long count) {
75+
requireTrackedModule(moduleType);
76+
if (count <= 0L) {
77+
resetViolationCount(playerId, moduleType);
78+
return;
79+
}
80+
violationCountMap.computeIfAbsent(playerId, ignored -> new ConcurrentHashMap<>())
81+
.put(moduleType, count);
82+
}
83+
84+
public void setViolationSnapshot(UUID playerId, Map<ModuleType, Long> counts) {
85+
Map<ModuleType, Long> sanitized = new EnumMap<>(ModuleType.class);
86+
for (Map.Entry<ModuleType, Long> entry : counts.entrySet()) {
87+
ModuleType moduleType = entry.getKey();
88+
long count = entry.getValue() == null ? 0L : entry.getValue();
89+
if (moduleType != null && moduleType.isViolationTracked() && count > 0L) {
90+
sanitized.put(moduleType, count);
91+
}
92+
}
93+
if (sanitized.isEmpty()) {
94+
violationCountMap.remove(playerId);
95+
return;
96+
}
97+
violationCountMap.put(playerId, new ConcurrentHashMap<>(sanitized));
98+
}
99+
73100
private static void requireTrackedModule(ModuleType moduleType) {
74101
if (!moduleType.isViolationTracked()) {
75102
throw new IllegalArgumentException(moduleType + " does not have a violation counter");

paper/src/main/java/io/wdsj/asw/bukkit/proxy/velocity/VelocityReceiver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class VelocityReceiver implements PluginMessageListener {
2020

2121
@Override
2222
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) {
23-
if (!setting(PluginSettings.HOOK_VELOCITY)) return;
23+
if (!setting(PluginSettings.ENABLE_ACTION_FORWARDING)) return;
2424
if (channel.equals(VelocityChannel.CHANNEL)) {
2525
ByteArrayDataInput input = ByteStreams.newDataInput(message);
2626
if (!input.readUTF().equals(AdvancedSensitiveWords.PLUGIN_VERSION) && !warned) {

0 commit comments

Comments
 (0)