9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-25 18:09:27 +00:00

Merge pull request #54 from jhqwqmc/dev

feat(network): 补充聊天命令拦截
This commit is contained in:
XiaoMoMi
2025-03-23 21:00:58 +08:00
committed by GitHub
9 changed files with 207 additions and 33 deletions

View File

@@ -0,0 +1,93 @@
package net.momirealms.craftengine.bukkit.font;
import com.google.gson.JsonElement;
import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.core.font.AbstractImageManager;
import net.momirealms.craftengine.core.util.AdventureHelper;
import net.momirealms.craftengine.core.util.CharacterUtils;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.lang.reflect.InvocationTargetException;
public class BukkitImageManager extends AbstractImageManager implements Listener {
private final BukkitCraftEngine plugin;
private final Object serializer;
public BukkitImageManager(BukkitCraftEngine plugin) {
super(plugin);
this.plugin = plugin;
try {
Object builder = Reflections.method$GsonComponentSerializer$builder.invoke(null);
this.serializer = Reflections.method$GsonComponentSerializer$Builder$build.invoke(builder);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public void delayedInit() {
Bukkit.getPluginManager().registerEvents(this, plugin.bootstrap());
}
@Override
public void disable() {
super.disable();
HandlerList.unregisterAll(this);
}
@EventHandler
@SuppressWarnings("UnstableApiUsage")
public void onChat(AsyncChatDecorateEvent event) {
if (event.player() == null) return;
this.processChatMessages(event);
}
@EventHandler
@SuppressWarnings("UnstableApiUsage")
public void onChatCommand(AsyncChatCommandDecorateEvent event) {
if (event.player() == null) return;
this.processChatMessages(event);
}
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent event) {
event.setMessage(processIllegalString(event.getMessage()));
}
@SuppressWarnings("UnstableApiUsage")
private void processChatMessages(AsyncChatDecorateEvent event) {
try {
Object originalMessage = Reflections.clazz$AdventureComponent.cast(Reflections.field$AsyncChatDecorateEvent$originalMessage.get(event));
JsonElement json = (JsonElement) Reflections.method$GsonComponentSerializer$serializeToTree.invoke(serializer, originalMessage);
String jsonMessage = AdventureHelper.jsonElementToStringJson(json);
if (!this.isDefaultFontInUse()) return;
String str = processIllegalString(jsonMessage);
Object component = Reflections.method$ComponentSerializer$deserialize.invoke(serializer, str);
Reflections.method$AsyncChatDecorateEvent$result.invoke(event, component);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private String processIllegalString(String string) {
char[] chars = string.toCharArray();
int[] codepoints = CharacterUtils.charsToCodePoints(chars);
int[] newCodepoints = new int[codepoints.length];
for (int i = 0; i < codepoints.length; i++) {
int codepoint = codepoints[i];
if (!this.isIllegalCharacter(codepoint)) {
newCodepoints[i] = codepoint;
} else {
newCodepoints[i] = '*';
}
}
return new String(newCodepoints, 0, newCodepoints.length);
}
}

View File

@@ -5,6 +5,7 @@ import net.momirealms.craftengine.bukkit.api.event.CraftEngineReloadEvent;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.block.behavior.BukkitBlockBehaviors;
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
import net.momirealms.craftengine.bukkit.font.BukkitImageManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.item.behavior.BukkitItemBehaviors;
import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager;
@@ -49,6 +50,7 @@ import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("unchecked")
public class BukkitCraftEngine extends CraftEngine {
@@ -56,6 +58,7 @@ public class BukkitCraftEngine extends CraftEngine {
private final JavaPlugin bootstrap;
private SchedulerTask tickTask;
private boolean successfullyLoaded = false;
private boolean successfullyEnabled = false;
private boolean requiresRestart = false;
private boolean hasMod = false;
private AntiGriefLib antiGrief;
@@ -103,6 +106,18 @@ public class BukkitCraftEngine extends CraftEngine {
@Override
public void enable() {
if (successfullyEnabled) {
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
logger().severe("Please do not restart plugins at runtime.");
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
Bukkit.getPluginManager().disablePlugin(this.bootstrap);
return;
}
this.successfullyEnabled = true;
if (this.hasMod && this.requiresRestart) {
logger().warn(" ");
logger().warn(" ");
@@ -138,6 +153,7 @@ public class BukkitCraftEngine extends CraftEngine {
super.worldManager = new BukkitWorldManager(this);
super.soundManager = new BukkitSoundManager(this);
super.vanillaLootManager = new BukkitVanillaLootManager(this);
this.imageManager = new BukkitImageManager(this);
super.enable();
// tick task
if (VersionHelper.isFolia()) {
@@ -175,6 +191,16 @@ public class BukkitCraftEngine extends CraftEngine {
public void disable() {
super.disable();
if (this.tickTask != null) this.tickTask.cancel();
if (!Bukkit.getServer().isStopping()) {
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
logger().severe("Please do not disable plugins at runtime.");
logger().severe(" ");
logger().severe(" ");
logger().severe(" ");
Bukkit.getServer().shutdown();
}
}
@Override

View File

@@ -125,7 +125,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener {
registerNMSPacketConsumer(PacketConsumers.MOVE_ENTITY, Reflections.clazz$ClientboundMoveEntityPacket$Pos);
registerNMSPacketConsumer(PacketConsumers.PICK_ITEM_FROM_ENTITY, Reflections.clazz$ServerboundPickItemFromEntityPacket);
registerNMSPacketConsumer(PacketConsumers.SOUND, Reflections.clazz$ClientboundSoundPacket);
registerNMSPacketConsumer(PacketConsumers.CHAT, Reflections.clazz$ServerboundChatPacket);
registerNMSPacketConsumer(PacketConsumers.RENAME_ITEM, Reflections.clazz$ServerboundRenameItemPacket);
registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, Reflections.clazz$ServerboundSignUpdatePacket);
registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());

View File

@@ -663,34 +663,6 @@ public class PacketConsumers {
}
};
// we handle it on packet level to prevent it from being captured by plugins (most are chat plugins)
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> CHAT = (user, event, packet) -> {
try {
String message = (String) Reflections.field$ServerboundChatPacket$message.get(packet);
if (message != null && !message.isEmpty()) {
ImageManager manager = CraftEngine.instance().imageManager();
if (!manager.isDefaultFontInUse()) return;
runIfContainsIllegalCharacter(message, manager, (s) -> {
event.setCancelled(true);
try {
Object newPacket = Reflections.constructor$ServerboundChatPacket.newInstance(
s,
Reflections.field$ServerboundChatPacket$timeStamp.get(packet),
Reflections.field$ServerboundChatPacket$salt.get(packet),
Reflections.field$ServerboundChatPacket$signature.get(packet),
Reflections.field$ServerboundChatPacket$lastSeenMessages.get(packet)
);
user.receivePacket(newPacket);
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to create replaced chat packet", e);
}
});
}
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to handle ServerboundChatPacket", e);
}
};
// we handle it on packet level to prevent it from being captured by plugins
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> RENAME_ITEM = (user, event, packet) -> {
try {
@@ -733,6 +705,7 @@ public class PacketConsumers {
};
private static void runIfContainsIllegalCharacter(String string, ImageManager manager, Consumer<String> callback) {
//noinspection DuplicatedCode
char[] chars = string.toCharArray();
int[] codepoints = CharacterUtils.charsToCodePoints(chars);
int[] newCodepoints = new int[codepoints.length];

View File

@@ -1,12 +1,14 @@
package net.momirealms.craftengine.bukkit.util;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonElement;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.util.ReflectionUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
@@ -5201,4 +5203,64 @@ public class Reflections {
clazz$ServerboundSignUpdatePacket, String[].class, 0
)
);
@SuppressWarnings("UnstableApiUsage")
public static final Field field$AsyncChatDecorateEvent$originalMessage = requireNonNull(
ReflectionUtils.getDeclaredField(AsyncChatDecorateEvent.class, "originalMessage")
);
public static final Class<?> clazz$ComponentSerializer = requireNonNull(
ReflectionUtils.getClazz(
"net{}kyori{}adventure{}text{}serializer{}ComponentSerializer".replace("{}", ".")
)
);
public static final Class<?> clazz$GsonComponentSerializer = requireNonNull(
ReflectionUtils.getClazz(
"net{}kyori{}adventure{}text{}serializer{}gson{}GsonComponentSerializer".replace("{}", ".")
)
);
public static final Class<?> clazz$GsonComponentSerializer$Builder = requireNonNull(
ReflectionUtils.getClazz(
"net{}kyori{}adventure{}text{}serializer{}gson{}GsonComponentSerializer$Builder".replace("{}", ".")
)
);
public static final Class<?> clazz$AdventureComponent = requireNonNull(
ReflectionUtils.getClazz(
"net{}kyori{}adventure{}text{}Component".replace("{}", ".")
)
);
public static final Method method$GsonComponentSerializer$builder = requireNonNull(
ReflectionUtils.getMethod(
clazz$GsonComponentSerializer, clazz$GsonComponentSerializer$Builder
)
);
public static final Method method$GsonComponentSerializer$Builder$build = requireNonNull(
ReflectionUtils.getMethod(
clazz$GsonComponentSerializer$Builder, clazz$GsonComponentSerializer
)
);
public static final Method method$GsonComponentSerializer$serializeToTree = requireNonNull(
ReflectionUtils.getMethod(
clazz$GsonComponentSerializer, JsonElement.class, clazz$AdventureComponent
)
);
public static final Method method$ComponentSerializer$deserialize = requireNonNull(
ReflectionUtils.getMethod(
clazz$ComponentSerializer, Object.class, Object.class
)
);
@SuppressWarnings("UnstableApiUsage")
public static final Method method$AsyncChatDecorateEvent$result = requireNonNull(
ReflectionUtils.getMethod(
AsyncChatDecorateEvent.class, void.class, clazz$AdventureComponent
)
);
}

View File

@@ -12,7 +12,7 @@ import java.nio.file.Path;
import java.util.*;
import java.util.function.BiFunction;
public class ImageManagerImpl implements ImageManager {
public abstract class AbstractImageManager implements ImageManager {
private final CraftEngine plugin;
// namespace:font font
private final HashMap<Key, Font> fonts = new HashMap<>();
@@ -22,7 +22,7 @@ public class ImageManagerImpl implements ImageManager {
private OffsetFont offsetFont;
public ImageManagerImpl(CraftEngine plugin) {
public AbstractImageManager(CraftEngine plugin) {
this.plugin = plugin;
}

View File

@@ -68,4 +68,6 @@ public interface ImageManager extends Reloadable, ConfigSectionParser {
default int loadingSequence() {
return LoadingSequence.FONT;
}
void delayedInit();
}

View File

@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.plugin;
import net.momirealms.craftengine.core.block.BlockManager;
import net.momirealms.craftengine.core.entity.furniture.FurnitureManager;
import net.momirealms.craftengine.core.font.ImageManager;
import net.momirealms.craftengine.core.font.ImageManagerImpl;
import net.momirealms.craftengine.core.font.AbstractImageManager;
import net.momirealms.craftengine.core.item.ItemManager;
import net.momirealms.craftengine.core.item.recipe.RecipeManager;
import net.momirealms.craftengine.core.loot.VanillaLootManager;
@@ -120,7 +120,6 @@ public abstract class CraftEngine implements Plugin {
@Override
public void enable() {
this.networkManager.enable();
this.imageManager = new ImageManagerImpl(this);
this.templateManager = new TemplateManagerImpl(this);
this.itemBrowserManager = new ItemBrowserManagerImpl(this);
this.commandManager.registerDefaultFeatures();
@@ -135,6 +134,7 @@ public abstract class CraftEngine implements Plugin {
this.worldManager.delayedInit();
this.packManager.delayedInit();
this.furnitureManager.delayedInit();
this.imageManager.delayedInit();
});
}

View File

@@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.util;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.gson.JsonElement;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
@@ -182,6 +183,24 @@ public class AdventureHelper {
return getGson().serialize(component);
}
/**
* Converts a JsonElement to a Component.
* @param gson the JsonElement to convert
* @return the resulting Component
*/
public static Component jsonElementToComponent(JsonElement gson) {
return GsonComponentSerializer.gson().deserializeFromTree(gson);
}
/**
* Converts a JsonElement to a JSON string.
* @param jsonElement the JsonElement to convert
* @return the JSON string representation
*/
public static String jsonElementToStringJson(JsonElement jsonElement) {
return componentToJson(jsonElementToComponent(jsonElement));
}
/**
* Checks if a character is a legacy color code.
*