From dd12fe4c00a539ac3be4461b9d9fc513ab38e6e6 Mon Sep 17 00:00:00 2001 From: John Etherfield Date: Sun, 4 Oct 2026 11:24:26 +0200 Subject: [PATCH] fix: prevent chat handler errors and corrupt stealth.json crashes --- build.gradle | 2 +- gradle.properties | 2 +- src/main/java/org/examp/john/AntiAbuse.java | 18 +++ src/main/java/org/examp/john/ChatHandler.java | 126 ++++++++++-------- src/main/java/org/examp/john/MSNexus.java | 1 + .../org/examp/john/mixin/CommandLogMixin.java | 2 +- .../examp/john/stealth/StealthStorage.java | 38 ++++-- 7 files changed, 122 insertions(+), 67 deletions(-) diff --git a/build.gradle b/build.gradle index 6a79a1d..9b39cf6 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ loom { splitEnvironmentSourceSets() mods { - "localchat" { + "msnexus" { sourceSet sourceSets.main sourceSet sourceSets.client } diff --git a/gradle.properties b/gradle.properties index cba7521..82e47c1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx2G minecraft_version=26.1.2 yarn_mappings=1.21.11+build.6 loader_version=0.19.2 -mod_version=1.0 +mod_version=1.1 maven_group=org.example1 archives_base_name=msnexus fabric_version=0.155.2+26.1.2 diff --git a/src/main/java/org/examp/john/AntiAbuse.java b/src/main/java/org/examp/john/AntiAbuse.java index db139a1..2d0663e 100644 --- a/src/main/java/org/examp/john/AntiAbuse.java +++ b/src/main/java/org/examp/john/AntiAbuse.java @@ -1,5 +1,6 @@ package org.examp.john; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.minecraft.server.level.ServerPlayer; import java.util.ArrayList; @@ -23,8 +24,18 @@ public class AntiAbuse { private static final int REPEAT_THRESHOLD = 5; private static final double DISTANCE_THRESHOLD = 50.0; private static final long TIME_WINDOW_MS = 5 * 60 * 1000; + private static final long ALERT_COOLDOWN_MS = 30 * 1000; private static final Map> history = new HashMap<>(); + private static final Map lastAlertTime = new HashMap<>(); + + public static void register() { + ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { + UUID uuid = handler.player.getUUID(); + history.remove(uuid); + lastAlertTime.remove(uuid); + }); + } public static void recordMessage(ServerPlayer player, String message) { UUID uuid = player.getUUID(); @@ -42,6 +53,7 @@ public class AntiAbuse { } private static void checkForAbuse(ServerPlayer player, List records) { + UUID uuid = player.getUUID(); Map counts = new HashMap<>(); Map bounds = new HashMap<>(); @@ -65,6 +77,12 @@ public class AntiAbuse { double spread = Math.max(b[1] - b[0], b[3] - b[2]); if (spread > DISTANCE_THRESHOLD) { + long now = System.currentTimeMillis(); + Long lastAlert = lastAlertTime.get(uuid); + if (lastAlert != null && now - lastAlert < ALERT_COOLDOWN_MS) { + continue; + } + lastAlertTime.put(uuid, now); MSNexus.magicModLog.warn( "[ANTI-ABUSE] Гравець {} ймовірно шукає інших: " + "'{}' ×{} разів, переміщення ~{} блоків", diff --git a/src/main/java/org/examp/john/ChatHandler.java b/src/main/java/org/examp/john/ChatHandler.java index 1e38aa0..5c565d8 100644 --- a/src/main/java/org/examp/john/ChatHandler.java +++ b/src/main/java/org/examp/john/ChatHandler.java @@ -20,74 +20,90 @@ import java.util.List; public class ChatHandler { public static void register() { + ServerMessageEvents.ALLOW_CHAT_MESSAGE.register((message, sender, params) -> { + try { + return handleMessage(message.signedContent(), sender); + } catch (Exception e) { + MSNexus.magicModLog.error( + "[CHAT] Не вдалося обробити повідомлення від {}", sender.getName().getString(), e); + return false; + } + }); + } + + private static boolean handleMessage(String text, ServerPlayer sender) { final String prefix = "!"; final float LOCAL_CHAT_RADIUS = 100; - ServerMessageEvents.ALLOW_CHAT_MESSAGE.register((message, sender, params) -> { - String text = message.signedContent(); - ServerLevel serverLevel = sender.level(); - List players = serverLevel.getServer().getPlayerList().getPlayers(); + if (text.isBlank()) { + return false; + } - MutableComponent formattedName = buildFormattedName(sender); + ServerLevel serverLevel = sender.level(); + List players = serverLevel.getServer().getPlayerList().getPlayers(); - if (text.startsWith(prefix)) { - String actualMessage = text.substring(1).trim(); - Component globalMessage = Component.empty() - .append(formattedName) - .append(Component.literal(": ")) - .append(buildFormattedMessage(actualMessage, players)); + MutableComponent formattedName = buildFormattedName(sender); - for (ServerPlayer player : players) { - player.sendSystemMessage(globalMessage); - } + if (text.startsWith(prefix)) { + String actualMessage = text.substring(1).trim(); + if (actualMessage.isEmpty()) { + return false; + } + Component globalMessage = Component.empty() + .append(formattedName) + .append(Component.literal(": ")) + .append(buildFormattedMessage(actualMessage, players)); - ChatLogger.logGlobal(sender, actualMessage, players); + for (ServerPlayer player : players) { + player.sendSystemMessage(globalMessage); + } - } else { - AntiAbuse.recordMessage(sender, text); + ChatLogger.logGlobal(sender, actualMessage, players); - if (StealthStorage.isEnabled(sender.getUUID())) { - sender.sendSystemMessage( - Component.literal("Вас ніхто не почув...") - .withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW) - ); - ChatLogger.logUnheard(sender, text); - return false; - } + } else { + AntiAbuse.recordMessage(sender, text); - List hearers = new ArrayList<>(); - for (ServerPlayer player : players) { - if (player != sender - && player.level() == serverLevel - && player.distanceTo(sender) <= LOCAL_CHAT_RADIUS) { - hearers.add(player); - } - } + if (StealthStorage.isEnabled(sender.getUUID())) { + sender.sendSystemMessage( + Component.literal("Вас ніхто не почув...") + .withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW) + ); + ChatLogger.logUnheard(sender, text); + return false; + } - if (!hearers.isEmpty()) { - Component localMessage = Component.empty() - .append(formattedName) - .append(Component.literal(": ").withStyle(ChatFormatting.GRAY)) - .append(buildFormattedMessage(text, players).withStyle(ChatFormatting.GRAY)); - - sender.sendSystemMessage(localMessage); - for (ServerPlayer hearer : hearers) { - hearer.sendSystemMessage(localMessage); - } - - ChatLogger.logLocal(sender, text, hearers); - - } else { - sender.sendSystemMessage( - Component.literal("Вас ніхто не почув, додайте \"!\" на початок для глобального чату") - .withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW) - ); - - ChatLogger.logUnheard(sender, text); + List hearers = new ArrayList<>(); + for (ServerPlayer player : players) { + if (player != sender + && player.level() == serverLevel + && player.distanceTo(sender) <= LOCAL_CHAT_RADIUS) { + hearers.add(player); } } - return false; - }); + + if (!hearers.isEmpty()) { + Component localMessage = Component.empty() + .append(formattedName) + .append(Component.literal(": ").withStyle(ChatFormatting.GRAY)) + .append(buildFormattedMessage(text, players).withStyle(ChatFormatting.GRAY)); + + sender.sendSystemMessage(localMessage); + for (ServerPlayer hearer : hearers) { + hearer.sendSystemMessage(localMessage); + } + + ChatLogger.logLocal(sender, text, hearers); + + } else { + sender.sendSystemMessage( + Component.literal("Вас ніхто не почув, додайте \"!\" на початок для глобального чату") + .withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW) + ); + + ChatLogger.logUnheard(sender, text); + } + } + return false; } /** diff --git a/src/main/java/org/examp/john/MSNexus.java b/src/main/java/org/examp/john/MSNexus.java index 4b067d9..f1c86b2 100644 --- a/src/main/java/org/examp/john/MSNexus.java +++ b/src/main/java/org/examp/john/MSNexus.java @@ -13,6 +13,7 @@ public class MSNexus implements ModInitializer { @Override public void onInitialize() { StealthStorage.load(); + AntiAbuse.register(); ChatHandler.register(); StealthCommand.register(); } diff --git a/src/main/java/org/examp/john/mixin/CommandLogMixin.java b/src/main/java/org/examp/john/mixin/CommandLogMixin.java index dc5e0f3..e159bf8 100644 --- a/src/main/java/org/examp/john/mixin/CommandLogMixin.java +++ b/src/main/java/org/examp/john/mixin/CommandLogMixin.java @@ -32,7 +32,7 @@ public class CommandLogMixin { CommandLogger.logCommand(player, command); } } catch (Exception e) { - org.examp.john.MSNexus.magicModLog.warn("[CMD] Не вдалося залогувати команду: {}", e.getMessage()); + org.examp.john.MSNexus.magicModLog.warn("[CMD] Не вдалося залогувати команду", e); } } } diff --git a/src/main/java/org/examp/john/stealth/StealthStorage.java b/src/main/java/org/examp/john/stealth/StealthStorage.java index c273f9f..10576f4 100644 --- a/src/main/java/org/examp/john/stealth/StealthStorage.java +++ b/src/main/java/org/examp/john/stealth/StealthStorage.java @@ -2,7 +2,7 @@ package org.examp.john.stealth; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.JsonSyntaxException; +import net.fabricmc.loader.api.FabricLoader; import org.examp.john.MSNexus; import java.io.IOException; @@ -11,6 +11,7 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -23,7 +24,9 @@ import java.util.UUID; public class StealthStorage { private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - private static final Path CONFIG_PATH = Path.of("config", "localchat", "stealth.json"); + private static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir() + .resolve("localchat") + .resolve("stealth.json"); private static Map data = new HashMap<>(); @@ -35,23 +38,33 @@ public class StealthStorage { return; } try (Reader reader = Files.newBufferedReader(CONFIG_PATH, StandardCharsets.UTF_8)) { - @SuppressWarnings("unchecked") - Map raw = GSON.fromJson(reader, Map.class); + Map raw = GSON.fromJson(reader, Map.class); data = new HashMap<>(); if (raw != null) { - for (Map.Entry entry : raw.entrySet()) { - data.put(UUID.fromString(entry.getKey()), entry.getValue()); + for (Map.Entry entry : raw.entrySet()) { + try { + UUID uuid = UUID.fromString(entry.getKey()); + if (entry.getValue() instanceof Boolean value) { + data.put(uuid, value); + } else { + MSNexus.magicModLog.warn( + "stealth.json: пропущено некоректне значення для {}", entry.getKey()); + } + } catch (IllegalArgumentException e) { + MSNexus.magicModLog.warn( + "stealth.json: пропущено некоректний ключ {}", entry.getKey()); + } } } } - } catch (IOException | JsonSyntaxException | IllegalArgumentException e) { + } catch (IOException | RuntimeException e) { MSNexus.magicModLog.warn("Не вдалося прочитати stealth.json: {}", e.getMessage()); data = new HashMap<>(); } } public static boolean isEnabled(UUID uuid) { - return data.getOrDefault(uuid, false); + return Boolean.TRUE.equals(data.get(uuid)); } public static boolean toggle(UUID uuid) { @@ -70,13 +83,20 @@ public class StealthStorage { private static void save() { try { Files.createDirectories(CONFIG_PATH.getParent()); - try (Writer writer = Files.newBufferedWriter(CONFIG_PATH, StandardCharsets.UTF_8)) { + Path tmp = CONFIG_PATH.resolveSibling(CONFIG_PATH.getFileName() + ".tmp"); + try (Writer writer = Files.newBufferedWriter(tmp, StandardCharsets.UTF_8)) { Map raw = new HashMap<>(); for (Map.Entry entry : data.entrySet()) { raw.put(entry.getKey().toString(), entry.getValue()); } GSON.toJson(raw, writer); } + try { + Files.move(tmp, CONFIG_PATH, + StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + Files.move(tmp, CONFIG_PATH, StandardCopyOption.REPLACE_EXISTING); + } } catch (IOException e) { MSNexus.magicModLog.error("Не вдалося зберегти stealth.json: {}", e.getMessage()); }