Skills/nms/nms-packet-interceptor/SKILL.md
透過 Netty ChannelDuplexHandler 注入玩家連線管線,攔截/修改 Clientbound 與 Serverbound 封包 / Intercept and modify packets via Netty pipeline injection
npx skillsauth add MrPippi/MPS nms-packet-interceptorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
nms-packet-interceptor
在玩家的 Netty 連線管線中注入自訂 ChannelDuplexHandler,於封包進入/離開伺服器時進行讀取、修改或取消。常用於反外掛、封包記錄、自訂通訊協議、偽造資訊等場景。
| 參數 | 範例 | 說明 |
|------|------|------|
| package_name | com.example.network | 產出類別所在 package |
| handler_name | PacketInterceptor | Handler 類別名稱 |
| manager_name | InterceptorManager | 管理器類別名稱 |
| handler_id | myplugin_interceptor | Netty pipeline 中的 handler 名稱(須唯一) |
PacketInterceptor.java — ChannelDuplexHandler 實作InterceptorManager.java — 監聽 Join/Quit 事件管理注入/移除InterceptorListener.java — Bukkit 事件監聽器參見 Skills/paper-nms/PLATFORM.md。Netty 隨 Paper 提供,無需額外依賴。
PacketInterceptor.javapackage com.example.network;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import net.minecraft.network.protocol.Packet;
import org.bukkit.entity.Player;
import java.util.UUID;
import java.util.function.BiFunction;
@SuppressWarnings("UnstableApiUsage")
public final class PacketInterceptor extends ChannelDuplexHandler {
private final UUID playerId;
private final BiFunction<Player, Packet<?>, Packet<?>> inboundFilter;
private final BiFunction<Player, Packet<?>, Packet<?>> outboundFilter;
public PacketInterceptor(
Player player,
BiFunction<Player, Packet<?>, Packet<?>> inboundFilter,
BiFunction<Player, Packet<?>, Packet<?>> outboundFilter
) {
this.playerId = player.getUniqueId();
this.inboundFilter = inboundFilter;
this.outboundFilter = outboundFilter;
}
/** Serverbound:客戶端 → 伺服器。 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof Packet<?> packet) {
Player player = org.bukkit.Bukkit.getPlayer(playerId);
if (player != null && inboundFilter != null) {
Packet<?> modified = inboundFilter.apply(player, packet);
if (modified == null) return; // 取消封包
msg = modified;
}
}
super.channelRead(ctx, msg);
}
/** Clientbound:伺服器 → 客戶端。 */
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof Packet<?> packet) {
Player player = org.bukkit.Bukkit.getPlayer(playerId);
if (player != null && outboundFilter != null) {
Packet<?> modified = outboundFilter.apply(player, packet);
if (modified == null) return; // 取消封包
msg = modified;
}
}
super.write(ctx, msg, promise);
}
}
InterceptorManager.javapackage com.example.network;
import io.netty.channel.Channel;
import net.minecraft.network.Connection;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.network.protocol.Packet;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import java.util.function.BiFunction;
@SuppressWarnings("UnstableApiUsage")
public final class InterceptorManager {
private static final String HANDLER_ID = "myplugin_interceptor";
private final BiFunction<Player, Packet<?>, Packet<?>> inboundFilter;
private final BiFunction<Player, Packet<?>, Packet<?>> outboundFilter;
public InterceptorManager(
BiFunction<Player, Packet<?>, Packet<?>> inboundFilter,
BiFunction<Player, Packet<?>, Packet<?>> outboundFilter
) {
this.inboundFilter = inboundFilter;
this.outboundFilter = outboundFilter;
}
/** 在玩家 join 時注入 handler 至 pipeline。 */
public void inject(Player player) {
Channel channel = getChannel(player);
if (channel == null || channel.pipeline().get(HANDLER_ID) != null) return;
PacketInterceptor interceptor = new PacketInterceptor(player, inboundFilter, outboundFilter);
// 放在 vanilla "packet_handler" 前面(即我們先看到封包)
channel.pipeline().addBefore("packet_handler", HANDLER_ID, interceptor);
}
/** 在玩家 quit 時移除 handler。 */
public void uninject(Player player) {
Channel channel = getChannel(player);
if (channel == null || channel.pipeline().get(HANDLER_ID) == null) return;
channel.eventLoop().execute(() -> {
if (channel.pipeline().get(HANDLER_ID) != null) {
channel.pipeline().remove(HANDLER_ID);
}
});
}
private Channel getChannel(Player player) {
ServerPlayer nms = ((CraftPlayer) player).getHandle();
Connection connection = nms.connection.connection;
return connection != null ? connection.channel : null;
}
}
InterceptorListener.javapackage com.example.network;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public final class InterceptorListener implements Listener {
private final InterceptorManager manager;
public InterceptorListener(InterceptorManager manager) {
this.manager = manager;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) {
manager.inject(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onQuit(PlayerQuitEvent event) {
manager.uninject(event.getPlayer());
}
}
src/main/java/com/example/
├── MyNmsPlugin.java
└── network/
├── PacketInterceptor.java
├── InterceptorManager.java
└── InterceptorListener.java
channelRead 與 write 皆在 Netty IO 執行緒執行,禁止存取 Level/Entity/呼叫阻塞 IOBukkit.getScheduler().runTask(plugin, () -> { ... })player.teleport() 等 Bukkit 同步 APIchannel.eventLoop().execute(),避免 pipeline race conditionSkills/_shared/nms-threading.md| 錯誤 | 原因 | 解法 |
|------|------|------|
| NoSuchElementException: packet_handler | Netty pipeline handler 名稱變更 | 檢查 Paper 對應版本的 Connection.java |
| Handler 未觸發 | 注入時機太晚(封包已流經) | 用 PlayerJoinEvent 的 LOWEST 優先級 |
| Handler 造成延遲 | Filter function 內有阻塞操作 | 將阻塞操作丟至 async task,filter 僅做輕量判斷 |
| IllegalStateException: Handler already added | 重複注入 | 每次 inject 前檢查 pipeline().get(HANDLER_ID) |
| Server 關閉時 ClosedChannelException | 關服流程中 pipeline 已關閉 | 忽略此錯誤,或在 onDisable 先 uninject 所有玩家 |
development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag