ForcePilot/backend/package/yuxi/channel/extensions/minecraft/version_adapter.py
Kris b3b6c95094 feat(channel): 添加 Minecraft 渠道扩展
新增 Minecraft 渠道扩展,支持在 Yuxi 平台中集成 Minecraft 游戏服务器渠道。

包含以下功能模块:
- client: Minecraft 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- auth: 认证管理
- accounts: 账户管理
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- protocol: Minecraft 协议处理
- rcon: RCON 远程控制
- keepalive: 连接保活
- version_adapter: 版本适配
- setup: 初始化设置
- types: 类型定义
2026-05-21 11:26:52 +08:00

445 lines
11 KiB
Python

from dataclasses import dataclass, field
@dataclass
class PacketDef:
packet_id: int
name: str
direction: str
fields: list[dict] = field(default_factory=list)
@dataclass
class VersionAdapter:
version: int
game_version: str
clientbound: dict[int, PacketDef] = field(default_factory=dict)
serverbound: dict[int, PacketDef] = field(default_factory=dict)
def get_clientbound(self, packet_id: int) -> PacketDef | None:
return self.clientbound.get(packet_id)
def get_serverbound(self, packet_id: int) -> PacketDef | None:
return self.serverbound.get(packet_id)
def cb(self, name: str) -> int | None:
for pkt in self.clientbound.values():
if pkt.name == name:
return pkt.packet_id
return None
def sb(self, name: str) -> int | None:
for pkt in self.serverbound.values():
if pkt.name == name:
return pkt.packet_id
return None
V769 = VersionAdapter(version=769, game_version="1.21.4")
V769.clientbound[0x00] = PacketDef(
0x00,
"status_response",
"clientbound",
[
{"name": "json_response", "type": "string"},
],
)
V769.clientbound[0x01] = PacketDef(
0x01,
"pong_response",
"clientbound",
[
{"name": "payload", "type": "long"},
],
)
V769.clientbound[0x02] = PacketDef(
0x02,
"login_success",
"clientbound",
[
{"name": "uuid", "type": "uuid"},
{"name": "username", "type": "string"},
{"name": "properties_count", "type": "varint"},
],
)
V769.clientbound[0x01] = PacketDef(
0x01,
"encryption_request",
"clientbound",
[
{"name": "server_id", "type": "string"},
{"name": "public_key_length", "type": "varint"},
{"name": "verify_token_length", "type": "varint"},
],
)
V769.clientbound[0x26] = PacketDef(
0x26,
"keep_alive",
"clientbound",
[
{"name": "keep_alive_id", "type": "long"},
],
)
V769.clientbound[0x37] = PacketDef(
0x37,
"player_chat_message",
"clientbound",
[
{"name": "sender_uuid", "type": "uuid"},
{"name": "index", "type": "varint"},
{"name": "message_signature_present", "type": "bool"},
],
)
V769.clientbound[0x6B] = PacketDef(
0x6B,
"system_chat_message",
"clientbound",
[
{"name": "content", "type": "chat"},
{"name": "overlay", "type": "bool"},
],
)
V769.clientbound[0x1D] = PacketDef(
0x1D,
"disconnect",
"clientbound",
[
{"name": "reason", "type": "chat"},
],
)
V769.clientbound[0x2B] = PacketDef(
0x2B,
"login_play",
"clientbound",
[
{"name": "entity_id", "type": "int"},
{"name": "hardcore", "type": "bool"},
{"name": "dimension_count", "type": "varint"},
{"name": "max_players", "type": "varint"},
{"name": "view_distance", "type": "varint"},
{"name": "simulation_distance", "type": "varint"},
{"name": "reduced_debug_info", "type": "bool"},
{"name": "enable_respawn_screen", "type": "bool"},
{"name": "do_limited_crafting", "type": "bool"},
{"name": "dimension_type", "type": "string"},
{"name": "dimension_name", "type": "string"},
{"name": "hashed_seed", "type": "long"},
{"name": "game_mode", "type": "byte"},
{"name": "previous_game_mode", "type": "byte"},
{"name": "is_debug", "type": "bool"},
{"name": "is_flat", "type": "bool"},
{"name": "death_location", "type": "optional_string"},
{"name": "portal_cooldown", "type": "varint"},
],
)
V769.clientbound[0x3E] = PacketDef(
0x3E,
"player_info_update",
"clientbound",
[
{"name": "actions", "type": "byte"},
{"name": "number_of_players", "type": "varint"},
],
)
V769.clientbound[0x45] = PacketDef(0x45, "respawn", "clientbound", [])
V769.serverbound[0x18] = PacketDef(
0x18,
"keep_alive",
"serverbound",
[
{"name": "keep_alive_id", "type": "long"},
],
)
V769.serverbound[0x05] = PacketDef(
0x05,
"chat_message",
"serverbound",
[
{"name": "message", "type": "string"},
],
)
V769.serverbound[0x04] = PacketDef(
0x04,
"chat_command",
"serverbound",
[
{"name": "command", "type": "string"},
],
)
V769.serverbound[0x1A] = PacketDef(
0x1A,
"set_player_position",
"serverbound",
[
{"name": "x", "type": "double"},
{"name": "feet_y", "type": "double"},
{"name": "z", "type": "double"},
{"name": "on_ground", "type": "bool"},
],
)
V769.serverbound[0x1B] = PacketDef(
0x1B,
"set_player_position_rotation",
"serverbound",
[
{"name": "x", "type": "double"},
{"name": "feet_y", "type": "double"},
{"name": "z", "type": "double"},
{"name": "yaw", "type": "float"},
{"name": "pitch", "type": "float"},
{"name": "on_ground", "type": "bool"},
],
)
V763 = VersionAdapter(version=763, game_version="1.20.4")
V763.clientbound[0x00] = PacketDef(
0x00,
"status_response",
"clientbound",
[
{"name": "json_response", "type": "string"},
],
)
V763.clientbound[0x01] = PacketDef(
0x01,
"pong_response",
"clientbound",
[
{"name": "payload", "type": "long"},
],
)
V763.clientbound[0x26] = PacketDef(
0x26,
"keep_alive",
"clientbound",
[
{"name": "keep_alive_id", "type": "long"},
],
)
V763.clientbound[0x38] = PacketDef(0x38, "player_chat_message", "clientbound", [])
V763.clientbound[0x64] = PacketDef(
0x64,
"system_chat_message",
"clientbound",
[
{"name": "content", "type": "chat"},
{"name": "overlay", "type": "bool"},
],
)
V763.clientbound[0x1A] = PacketDef(
0x1A,
"disconnect",
"clientbound",
[
{"name": "reason", "type": "chat"},
],
)
V763.serverbound[0x12] = PacketDef(
0x12,
"keep_alive",
"serverbound",
[
{"name": "keep_alive_id", "type": "long"},
],
)
V763.serverbound[0x05] = PacketDef(
0x05,
"chat_message",
"serverbound",
[
{"name": "message", "type": "string"},
],
)
V763.serverbound[0x04] = PacketDef(
0x04,
"chat_command",
"serverbound",
[
{"name": "command", "type": "string"},
],
)
V767 = VersionAdapter(version=767, game_version="1.21-1.21.1")
V767.clientbound = dict(V769.clientbound)
V767.serverbound = dict(V769.serverbound)
V768 = VersionAdapter(version=768, game_version="1.21.2-1.21.3")
V768.clientbound = dict(V769.clientbound)
V768.serverbound = dict(V769.serverbound)
V770 = VersionAdapter(version=770, game_version="1.21.5")
V770.clientbound = {
0x00: PacketDef(
0x00,
"status_response",
"clientbound",
[
{"name": "json_response", "type": "string"},
],
),
0x01: PacketDef(
0x01,
"pong_response",
"clientbound",
[
{"name": "payload", "type": "long"},
],
),
0x02: PacketDef(
0x02,
"login_success",
"clientbound",
[
{"name": "uuid", "type": "uuid"},
{"name": "username", "type": "string"},
{"name": "properties_count", "type": "varint"},
],
),
0x26: PacketDef(
0x26,
"keep_alive",
"clientbound",
[
{"name": "keep_alive_id", "type": "long"},
],
),
0x3C: PacketDef(
0x3C,
"player_chat_message",
"clientbound",
[
{"name": "sender_uuid", "type": "uuid"},
{"name": "index", "type": "varint"},
{"name": "message_signature_present", "type": "bool"},
],
),
0x6C: PacketDef(
0x6C,
"system_chat_message",
"clientbound",
[
{"name": "content", "type": "chat"},
{"name": "overlay", "type": "bool"},
],
),
0x1C: PacketDef(
0x1C,
"disconnect",
"clientbound",
[
{"name": "reason", "type": "chat"},
],
),
0x2B: PacketDef(
0x2B,
"login_play",
"clientbound",
[
{"name": "entity_id", "type": "int"},
{"name": "hardcore", "type": "bool"},
{"name": "dimension_count", "type": "varint"},
{"name": "max_players", "type": "varint"},
{"name": "view_distance", "type": "varint"},
{"name": "simulation_distance", "type": "varint"},
{"name": "reduced_debug_info", "type": "bool"},
{"name": "enable_respawn_screen", "type": "bool"},
{"name": "do_limited_crafting", "type": "bool"},
{"name": "dimension_type", "type": "string"},
{"name": "dimension_name", "type": "string"},
{"name": "hashed_seed", "type": "long"},
{"name": "game_mode", "type": "byte"},
{"name": "previous_game_mode", "type": "byte"},
{"name": "is_debug", "type": "bool"},
{"name": "is_flat", "type": "bool"},
{"name": "death_location", "type": "optional_string"},
{"name": "portal_cooldown", "type": "varint"},
],
),
0x3E: PacketDef(
0x3E,
"player_info_update",
"clientbound",
[
{"name": "actions", "type": "byte"},
{"name": "number_of_players", "type": "varint"},
],
),
0x45: PacketDef(0x45, "respawn", "clientbound", []),
}
V770.serverbound = {
0x18: PacketDef(
0x18,
"keep_alive",
"serverbound",
[
{"name": "keep_alive_id", "type": "long"},
],
),
0x06: PacketDef(
0x06,
"chat_message",
"serverbound",
[
{"name": "message", "type": "string"},
],
),
0x05: PacketDef(
0x05,
"chat_command",
"serverbound",
[
{"name": "command", "type": "string"},
],
),
0x1A: PacketDef(
0x1A,
"set_player_position",
"serverbound",
[
{"name": "x", "type": "double"},
{"name": "feet_y", "type": "double"},
{"name": "z", "type": "double"},
{"name": "on_ground", "type": "bool"},
],
),
0x1B: PacketDef(
0x1B,
"set_player_position_rotation",
"serverbound",
[
{"name": "x", "type": "double"},
{"name": "feet_y", "type": "double"},
{"name": "z", "type": "double"},
{"name": "yaw", "type": "float"},
{"name": "pitch", "type": "float"},
{"name": "on_ground", "type": "bool"},
],
),
}
PROTOCOL_REGISTRY: dict[int, VersionAdapter] = {
763: V763,
767: V767,
768: V768,
769: V769,
770: V770,
}
DEFAULT_PROTOCOL = 769
def get_adapter(version: int | str) -> VersionAdapter:
if isinstance(version, str) and version == "auto":
return V769
if isinstance(version, str):
try:
version = int(version)
except ValueError:
return V769
return PROTOCOL_REGISTRY.get(version, V769)