ygopro-msg-encode 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +4 -0
- package/.eslintrc.js +25 -0
- package/.prettierrc +4 -0
- package/AGENTS.md +12 -0
- package/CHANGES.md +270 -0
- package/CTOS_STOC_IMPLEMENTATION.md +279 -0
- package/FINAL_SUMMARY.md +357 -0
- package/FULL_PAYLOAD_SUMMARY.md +491 -0
- package/FULL_PAYLOAD_UPDATE.md +598 -0
- package/IMPLEMENTATION_SUMMARY.md +294 -0
- package/LICENSE +22 -0
- package/MSG_IMPLEMENTATION_SUMMARY.md +358 -0
- package/OPPONENT_VIEW_SUMMARY.md +387 -0
- package/PROJECT_COMPLETE.md +565 -0
- package/QUICK_REFERENCE.md +352 -0
- package/README.md +494 -0
- package/REAL_IP_STRING_UPDATE.md +289 -0
- package/TESTS_MIGRATION.md +342 -0
- package/VARIABLE_LENGTH_STRINGS.md +224 -0
- package/VARIABLE_LENGTH_UPDATE.md +229 -0
- package/dist/index.cjs +5131 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +5185 -0
- package/dist/index.mjs.map +7 -0
- package/dist/src/binary/binary-meta.d.ts +18 -0
- package/dist/src/binary/fill-binary-fields.d.ts +2 -0
- package/dist/src/metadata.d.ts +10 -0
- package/dist/src/proto-base/payload-base.d.ts +8 -0
- package/dist/src/proto-base/registry-base.d.ts +13 -0
- package/dist/src/protos/common/host-info.d.ts +12 -0
- package/dist/src/protos/common/index.d.ts +1 -0
- package/dist/src/protos/ctos/base.d.ts +17 -0
- package/dist/src/protos/ctos/index.d.ts +3 -0
- package/dist/src/protos/ctos/proto/chat.d.ts +9 -0
- package/dist/src/protos/ctos/proto/create-game.d.ts +8 -0
- package/dist/src/protos/ctos/proto/external-address.d.ts +12 -0
- package/dist/src/protos/ctos/proto/hand-result.d.ts +5 -0
- package/dist/src/protos/ctos/proto/hs-notready.d.ts +4 -0
- package/dist/src/protos/ctos/proto/hs-ready.d.ts +4 -0
- package/dist/src/protos/ctos/proto/hs-start.d.ts +4 -0
- package/dist/src/protos/ctos/proto/hs-toduelist.d.ts +4 -0
- package/dist/src/protos/ctos/proto/hs-toobserver.d.ts +4 -0
- package/dist/src/protos/ctos/proto/index.d.ts +19 -0
- package/dist/src/protos/ctos/proto/join-game.d.ts +7 -0
- package/dist/src/protos/ctos/proto/kick.d.ts +5 -0
- package/dist/src/protos/ctos/proto/leave-game.d.ts +4 -0
- package/dist/src/protos/ctos/proto/player-info.d.ts +5 -0
- package/dist/src/protos/ctos/proto/request-field.d.ts +4 -0
- package/dist/src/protos/ctos/proto/response.d.ts +7 -0
- package/dist/src/protos/ctos/proto/surrender.d.ts +4 -0
- package/dist/src/protos/ctos/proto/time-confirm.d.ts +4 -0
- package/dist/src/protos/ctos/proto/tp-result.d.ts +5 -0
- package/dist/src/protos/ctos/proto/update-deck.d.ts +11 -0
- package/dist/src/protos/ctos/registry.d.ts +3 -0
- package/dist/src/protos/msg/base.d.ts +9 -0
- package/dist/src/protos/msg/index.d.ts +3 -0
- package/dist/src/protos/msg/proto/add-counter.d.ts +9 -0
- package/dist/src/protos/msg/proto/announce-attrib.d.ts +7 -0
- package/dist/src/protos/msg/proto/announce-card.d.ts +7 -0
- package/dist/src/protos/msg/proto/announce-number.d.ts +7 -0
- package/dist/src/protos/msg/proto/announce-race.d.ts +7 -0
- package/dist/src/protos/msg/proto/attack-disabled.d.ts +4 -0
- package/dist/src/protos/msg/proto/attack.d.ts +12 -0
- package/dist/src/protos/msg/proto/battle.d.ts +18 -0
- package/dist/src/protos/msg/proto/become-target.d.ts +6 -0
- package/dist/src/protos/msg/proto/cancel-target.d.ts +11 -0
- package/dist/src/protos/msg/proto/card-hint.d.ts +10 -0
- package/dist/src/protos/msg/proto/card-query.d.ts +38 -0
- package/dist/src/protos/msg/proto/card-target.d.ts +11 -0
- package/dist/src/protos/msg/proto/chain-disabled.d.ts +5 -0
- package/dist/src/protos/msg/proto/chain-end.d.ts +4 -0
- package/dist/src/protos/msg/proto/chain-negated.d.ts +5 -0
- package/dist/src/protos/msg/proto/chain-solved.d.ts +5 -0
- package/dist/src/protos/msg/proto/chain-solving.d.ts +5 -0
- package/dist/src/protos/msg/proto/chained.d.ts +5 -0
- package/dist/src/protos/msg/proto/chaining.d.ts +12 -0
- package/dist/src/protos/msg/proto/confirm-cards.d.ts +16 -0
- package/dist/src/protos/msg/proto/confirm-decktop.d.ts +14 -0
- package/dist/src/protos/msg/proto/confirm-extratop.d.ts +14 -0
- package/dist/src/protos/msg/proto/damage-step-end.d.ts +4 -0
- package/dist/src/protos/msg/proto/damage-step-start.d.ts +4 -0
- package/dist/src/protos/msg/proto/damage.d.ts +6 -0
- package/dist/src/protos/msg/proto/deck-top.d.ts +8 -0
- package/dist/src/protos/msg/proto/draw.d.ts +8 -0
- package/dist/src/protos/msg/proto/equip.d.ts +12 -0
- package/dist/src/protos/msg/proto/field-disabled.d.ts +5 -0
- package/dist/src/protos/msg/proto/flipsummoned.d.ts +4 -0
- package/dist/src/protos/msg/proto/flipsummoning.d.ts +9 -0
- package/dist/src/protos/msg/proto/hand-res.d.ts +5 -0
- package/dist/src/protos/msg/proto/hint.d.ts +7 -0
- package/dist/src/protos/msg/proto/index.d.ts +85 -0
- package/dist/src/protos/msg/proto/lpupdate.d.ts +6 -0
- package/dist/src/protos/msg/proto/match-kill.d.ts +5 -0
- package/dist/src/protos/msg/proto/missed-effect.d.ts +7 -0
- package/dist/src/protos/msg/proto/move.d.ts +14 -0
- package/dist/src/protos/msg/proto/new-phase.d.ts +5 -0
- package/dist/src/protos/msg/proto/new-turn.d.ts +5 -0
- package/dist/src/protos/msg/proto/pay-lpcost.d.ts +6 -0
- package/dist/src/protos/msg/proto/player-hint.d.ts +7 -0
- package/dist/src/protos/msg/proto/pos-change.d.ts +12 -0
- package/dist/src/protos/msg/proto/random-selected.d.ts +7 -0
- package/dist/src/protos/msg/proto/recover.d.ts +6 -0
- package/dist/src/protos/msg/proto/reload-field.d.ts +36 -0
- package/dist/src/protos/msg/proto/remove-counter.d.ts +9 -0
- package/dist/src/protos/msg/proto/reset-time.d.ts +6 -0
- package/dist/src/protos/msg/proto/retry.d.ts +4 -0
- package/dist/src/protos/msg/proto/reverse-deck.d.ts +4 -0
- package/dist/src/protos/msg/proto/rock-paper-scissors.d.ts +5 -0
- package/dist/src/protos/msg/proto/select-battlecmd.d.ts +25 -0
- package/dist/src/protos/msg/proto/select-card.d.ts +17 -0
- package/dist/src/protos/msg/proto/select-chain.d.ts +19 -0
- package/dist/src/protos/msg/proto/select-counter.d.ts +17 -0
- package/dist/src/protos/msg/proto/select-disfield.d.ts +7 -0
- package/dist/src/protos/msg/proto/select-effectyn.d.ts +11 -0
- package/dist/src/protos/msg/proto/select-idlecmd.d.ts +37 -0
- package/dist/src/protos/msg/proto/select-option.d.ts +7 -0
- package/dist/src/protos/msg/proto/select-place.d.ts +7 -0
- package/dist/src/protos/msg/proto/select-position.d.ts +7 -0
- package/dist/src/protos/msg/proto/select-sum.d.ts +20 -0
- package/dist/src/protos/msg/proto/select-tribute.d.ts +18 -0
- package/dist/src/protos/msg/proto/select-unselect-card.d.ts +20 -0
- package/dist/src/protos/msg/proto/select-yesno.d.ts +6 -0
- package/dist/src/protos/msg/proto/set.d.ts +5 -0
- package/dist/src/protos/msg/proto/shuffle-deck.d.ts +5 -0
- package/dist/src/protos/msg/proto/shuffle-extra.d.ts +8 -0
- package/dist/src/protos/msg/proto/shuffle-hand.d.ts +8 -0
- package/dist/src/protos/msg/proto/shuffle-set-card.d.ts +17 -0
- package/dist/src/protos/msg/proto/sort-card.d.ts +13 -0
- package/dist/src/protos/msg/proto/spsummoned.d.ts +4 -0
- package/dist/src/protos/msg/proto/spsummoning.d.ts +9 -0
- package/dist/src/protos/msg/proto/start.d.ts +14 -0
- package/dist/src/protos/msg/proto/summoned.d.ts +4 -0
- package/dist/src/protos/msg/proto/summoning.d.ts +9 -0
- package/dist/src/protos/msg/proto/swap-grave-deck.d.ts +5 -0
- package/dist/src/protos/msg/proto/swap.d.ts +12 -0
- package/dist/src/protos/msg/proto/tag-swap.d.ts +14 -0
- package/dist/src/protos/msg/proto/toss-coin.d.ts +7 -0
- package/dist/src/protos/msg/proto/toss-dice.d.ts +7 -0
- package/dist/src/protos/msg/proto/update-card.d.ts +14 -0
- package/dist/src/protos/msg/proto/update-data.d.ts +12 -0
- package/dist/src/protos/msg/proto/waiting.d.ts +4 -0
- package/dist/src/protos/msg/proto/win.d.ts +6 -0
- package/dist/src/protos/msg/registry.d.ts +3 -0
- package/dist/src/protos/stoc/base.d.ts +17 -0
- package/dist/src/protos/stoc/index.d.ts +3 -0
- package/dist/src/protos/stoc/proto/change-side.d.ts +4 -0
- package/dist/src/protos/stoc/proto/chat.d.ts +10 -0
- package/dist/src/protos/stoc/proto/create-game.d.ts +5 -0
- package/dist/src/protos/stoc/proto/deck-count.d.ts +5 -0
- package/dist/src/protos/stoc/proto/duel-end.d.ts +4 -0
- package/dist/src/protos/stoc/proto/duel-start.d.ts +4 -0
- package/dist/src/protos/stoc/proto/error-msg.d.ts +6 -0
- package/dist/src/protos/stoc/proto/field-finish.d.ts +4 -0
- package/dist/src/protos/stoc/proto/game-msg.d.ts +9 -0
- package/dist/src/protos/stoc/proto/hand-result.d.ts +6 -0
- package/dist/src/protos/stoc/proto/hs-player-change.d.ts +5 -0
- package/dist/src/protos/stoc/proto/hs-player-enter.d.ts +6 -0
- package/dist/src/protos/stoc/proto/hs-watch-change.d.ts +5 -0
- package/dist/src/protos/stoc/proto/index.d.ts +24 -0
- package/dist/src/protos/stoc/proto/join-game.d.ts +6 -0
- package/dist/src/protos/stoc/proto/leave-game.d.ts +5 -0
- package/dist/src/protos/stoc/proto/replay.d.ts +11 -0
- package/dist/src/protos/stoc/proto/select-hand.d.ts +4 -0
- package/dist/src/protos/stoc/proto/select-tp.d.ts +4 -0
- package/dist/src/protos/stoc/proto/srvpro-roomlist.d.ts +18 -0
- package/dist/src/protos/stoc/proto/teammate-surrender.d.ts +4 -0
- package/dist/src/protos/stoc/proto/time-limit.d.ts +6 -0
- package/dist/src/protos/stoc/proto/tp-result.d.ts +4 -0
- package/dist/src/protos/stoc/proto/type-change.d.ts +5 -0
- package/dist/src/protos/stoc/proto/waiting-side.d.ts +4 -0
- package/dist/src/protos/stoc/registry.d.ts +3 -0
- package/dist/src/vendor/ocgcore-constants.d.ts +360 -0
- package/dist/src/vendor/script-constants.d.ts +836 -0
- package/index.ts +6 -0
- package/package.json +74 -0
- package/scripts/gen-constants.js +156 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# 可变长度字符串协议实现说明
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
以下三个协议使用了自定义的可变长度 UTF-16 字符串实现:
|
|
6
|
+
1. `CTOS_CHAT` (0x16)
|
|
7
|
+
2. `STOC_CHAT` (0x19)
|
|
8
|
+
3. `CTOS_EXTERNAL_ADDRESS` (0x17)
|
|
9
|
+
|
|
10
|
+
## 实现原则
|
|
11
|
+
|
|
12
|
+
### 序列化(toPayload)
|
|
13
|
+
- ✅ 只发送实际内容的字节数,不填充到最大长度
|
|
14
|
+
- ✅ 在字符串末尾添加一个 UTF-16 null terminator (`\0\0`, 即 2 字节的 0)
|
|
15
|
+
- ✅ 对于有前缀字段的协议(如 player_type, real_ip),先写入前缀字段
|
|
16
|
+
|
|
17
|
+
### 反序列化(fromPayload)
|
|
18
|
+
- ✅ 解析所有可用字节
|
|
19
|
+
- ✅ 不要求末尾必须有 `\0\0`
|
|
20
|
+
- ✅ 自动移除末尾的所有 null 字符
|
|
21
|
+
- ✅ 对于有前缀字段的协议,先读取前缀字段,然后解析剩余字节为字符串
|
|
22
|
+
|
|
23
|
+
## 协议详情
|
|
24
|
+
|
|
25
|
+
### 1. CTOS_CHAT (0x16)
|
|
26
|
+
|
|
27
|
+
**格式**: `[UTF-16LE 字符串]`
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
export class YGOProCtosChat extends YGOProCtosBase {
|
|
31
|
+
static identifier = 0x16;
|
|
32
|
+
msg: string;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**序列化示例**:
|
|
37
|
+
- 原始消息: `"Hello"` (5 个字符)
|
|
38
|
+
- 输出: 12 字节 = 5 字符 × 2 + 2 字节 null terminator
|
|
39
|
+
- 字节序列: `[0x48, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x00, 0x00]`
|
|
40
|
+
|
|
41
|
+
**反序列化**:
|
|
42
|
+
- 接受任意长度的 UTF-16LE 编码字节
|
|
43
|
+
- 自动移除末尾的 null 字符
|
|
44
|
+
|
|
45
|
+
### 2. STOC_CHAT (0x19)
|
|
46
|
+
|
|
47
|
+
**格式**: `[player_type 2 bytes][UTF-16LE 字符串]`
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
export class YGOProStocChat extends YGOProStocBase {
|
|
51
|
+
static identifier = 0x19;
|
|
52
|
+
player_type: number; // uint16_t
|
|
53
|
+
msg: string;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**序列化示例**:
|
|
58
|
+
- player_type: `0x0010` (Observer + Player 0)
|
|
59
|
+
- 原始消息: `"GG"` (2 个字符)
|
|
60
|
+
- 输出: 8 字节 = 2 (player_type) + 2 字符 × 2 + 2 字节 null terminator
|
|
61
|
+
- 字节序列: `[0x10, 0x00, 0x47, 0x00, 0x47, 0x00, 0x00, 0x00]`
|
|
62
|
+
|
|
63
|
+
**反序列化**:
|
|
64
|
+
- 前 2 字节: player_type (little endian)
|
|
65
|
+
- 剩余字节: UTF-16LE 字符串(自动移除末尾 null)
|
|
66
|
+
|
|
67
|
+
### 3. CTOS_EXTERNAL_ADDRESS (0x17)
|
|
68
|
+
|
|
69
|
+
**格式**: `[real_ip 4 bytes][UTF-16LE 字符串]`
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
export class YGOProCtosExternalAddress extends YGOProCtosBase {
|
|
73
|
+
static identifier = 0x17;
|
|
74
|
+
real_ip: string; // IPv4 address as string (e.g., "127.0.0.1")
|
|
75
|
+
hostname: string;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**序列化示例**:
|
|
80
|
+
- real_ip: `"127.0.0.1"` (自动转换为网络序 0x7F000001)
|
|
81
|
+
- 原始主机名: `"example.com"` (11 个字符)
|
|
82
|
+
- 输出: 28 字节 = 4 (real_ip) + 11 字符 × 2 + 2 字节 null terminator
|
|
83
|
+
- 字节序列: `[0x7F, 0x00, 0x00, 0x01, 0x65, 0x00, 0x78, 0x00, ...]`
|
|
84
|
+
- 注意:real_ip 使用**大端序(网络序)**
|
|
85
|
+
|
|
86
|
+
**反序列化**:
|
|
87
|
+
- 前 4 字节: real_ip (big endian / network byte order,自动转换为字符串)
|
|
88
|
+
- 剩余字节: UTF-16LE 字符串(自动移除末尾 null)
|
|
89
|
+
|
|
90
|
+
**IPv6 映射支持**:
|
|
91
|
+
- 输入: `"::ffff:127.0.0.1"` → 自动提取为 `"127.0.0.1"`
|
|
92
|
+
- 序列化时转换为标准 IPv4 格式
|
|
93
|
+
|
|
94
|
+
## 优势
|
|
95
|
+
|
|
96
|
+
### 节省带宽
|
|
97
|
+
- ❌ 固定长度方式: CTOS_CHAT 总是发送 512 字节 (256 × 2)
|
|
98
|
+
- ✅ 可变长度方式: "Hello" 只需 12 字节
|
|
99
|
+
|
|
100
|
+
### 兼容性
|
|
101
|
+
- ✅ 始终添加 null terminator,确保 C/C++ 字符串兼容
|
|
102
|
+
- ✅ 解析时不要求 null terminator,适应各种数据源
|
|
103
|
+
- ✅ 自动处理末尾 null 字符,避免显示问题
|
|
104
|
+
|
|
105
|
+
## 对比表
|
|
106
|
+
|
|
107
|
+
| 特性 | 固定长度实现 | 可变长度实现 |
|
|
108
|
+
|------|--------------|--------------|
|
|
109
|
+
| CTOS_CHAT 大小 | 512 bytes | 实际内容 × 2 + 2 |
|
|
110
|
+
| STOC_CHAT 大小 | 514 bytes | 2 + 实际内容 × 2 + 2 |
|
|
111
|
+
| EXTERNAL_ADDRESS 大小 | 516 bytes | 4 + 实际内容 × 2 + 2 |
|
|
112
|
+
| real_ip 类型 | uint32_t | string (IPv4) |
|
|
113
|
+
| real_ip 示例 | 0x7F000001 | "127.0.0.1" |
|
|
114
|
+
| 带宽效率 | 低 | 高 |
|
|
115
|
+
| Null terminator | 可选 | 始终添加 |
|
|
116
|
+
| 解析灵活性 | 低 | 高 |
|
|
117
|
+
|
|
118
|
+
## 测试
|
|
119
|
+
|
|
120
|
+
运行测试脚本验证实现:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx tsx test-chat-protocols.ts
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
测试内容:
|
|
127
|
+
- ✅ CTOS_CHAT 序列化和反序列化
|
|
128
|
+
- ✅ STOC_CHAT 序列化和反序列化
|
|
129
|
+
- ✅ CTOS_EXTERNAL_ADDRESS 序列化和反序列化
|
|
130
|
+
- ✅ 无 null terminator 的数据解析
|
|
131
|
+
- ✅ 验证 null terminator 的存在
|
|
132
|
+
- ✅ 验证前缀字段正确编码
|
|
133
|
+
|
|
134
|
+
## 实现细节
|
|
135
|
+
|
|
136
|
+
### UTF-16LE 编码
|
|
137
|
+
|
|
138
|
+
使用 JavaScript 的 `TextDecoder` 和手动编码:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// 编码
|
|
142
|
+
const utf16 = new Uint16Array(text.length + 1);
|
|
143
|
+
for (let i = 0; i < text.length; i++) {
|
|
144
|
+
utf16[i] = text.charCodeAt(i);
|
|
145
|
+
}
|
|
146
|
+
utf16[text.length] = 0; // null terminator
|
|
147
|
+
const bytes = new Uint8Array(utf16.buffer);
|
|
148
|
+
|
|
149
|
+
// 解码
|
|
150
|
+
const decoder = new TextDecoder('utf-16le');
|
|
151
|
+
const text = decoder.decode(data).replace(/\0+$/, '');
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 字节序(Byte Order)
|
|
155
|
+
|
|
156
|
+
**Little Endian** - 用于 player_type:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// 写入 uint16_t (little endian)
|
|
160
|
+
result[0] = value & 0xff;
|
|
161
|
+
result[1] = (value >> 8) & 0xff;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Big Endian (Network Byte Order)** - 用于 real_ip:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// 写入 uint32_t (big endian / network byte order)
|
|
168
|
+
result[0] = (value >> 24) & 0xff;
|
|
169
|
+
result[1] = (value >> 16) & 0xff;
|
|
170
|
+
result[2] = (value >> 8) & 0xff;
|
|
171
|
+
result[3] = value & 0xff;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
⚠️ **重要**: `real_ip` 字段使用**网络字节序(大端序)**,这是 IPv4 地址的标准格式。
|
|
175
|
+
|
|
176
|
+
### IPv4 字符串转换
|
|
177
|
+
|
|
178
|
+
`CTOS_EXTERNAL_ADDRESS` 的 `real_ip` 字段使用字符串格式,自动处理转换:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// IPv4 string to uint32 (network byte order)
|
|
182
|
+
private ipToUint32(ip: string): number {
|
|
183
|
+
// Handle IPv6-mapped IPv4: "::ffff:127.0.0.1" -> "127.0.0.1"
|
|
184
|
+
let ipv4 = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
|
185
|
+
|
|
186
|
+
const parts = ipv4.split('.');
|
|
187
|
+
const bytes = parts.map(p => parseInt(p, 10));
|
|
188
|
+
|
|
189
|
+
// Network byte order (big endian)
|
|
190
|
+
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// uint32 to IPv4 string
|
|
194
|
+
private uint32ToIp(value: number): string {
|
|
195
|
+
const byte1 = (value >>> 24) & 0xff;
|
|
196
|
+
const byte2 = (value >>> 16) & 0xff;
|
|
197
|
+
const byte3 = (value >>> 8) & 0xff;
|
|
198
|
+
const byte4 = value & 0xff;
|
|
199
|
+
return `${byte1}.${byte2}.${byte3}.${byte4}`;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**使用示例**:
|
|
204
|
+
```typescript
|
|
205
|
+
const ext = new YGOProCtosExternalAddress();
|
|
206
|
+
ext.real_ip = "127.0.0.1"; // 标准 IPv4
|
|
207
|
+
ext.real_ip = "::ffff:192.168.1.1"; // IPv6 映射的 IPv4
|
|
208
|
+
|
|
209
|
+
const payload = ext.toPayload();
|
|
210
|
+
// real_ip "127.0.0.1" → bytes [0x7F, 0x00, 0x00, 0x01]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 注意事项
|
|
214
|
+
|
|
215
|
+
1. **字符编码**: 仅支持 BMP(Basic Multilingual Plane)字符,不支持代理对(Surrogate Pairs)
|
|
216
|
+
2. **最大长度**: 虽然是可变长度,但应该避免发送过长的字符串(建议 < 256 字符)
|
|
217
|
+
3. **Null terminator**: 序列化时始终添加,但反序列化时不依赖它
|
|
218
|
+
4. **空字符串**: 空字符串将被序列化为仅 2 字节的 null terminator
|
|
219
|
+
5. **IPv4 格式**: `real_ip` 仅支持 IPv4 地址,IPv6 映射格式会自动提取 IPv4 部分
|
|
220
|
+
6. **IP 验证**: 无效的 IP 地址(如 "abc.def")会被转换为 `0.0.0.0`
|
|
221
|
+
|
|
222
|
+
## 更新日期
|
|
223
|
+
|
|
224
|
+
2026-02-02
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# 可变长度字符串协议更新
|
|
2
|
+
|
|
3
|
+
## 更新日期
|
|
4
|
+
2026-02-02
|
|
5
|
+
|
|
6
|
+
## 更新内容
|
|
7
|
+
|
|
8
|
+
### 修改的协议
|
|
9
|
+
|
|
10
|
+
将以下三个协议从固定长度实现改为可变长度实现:
|
|
11
|
+
|
|
12
|
+
1. **CTOS_CHAT** (0x16)
|
|
13
|
+
- 文件: `src/protos/ctos/proto/chat.ts`
|
|
14
|
+
- 从: `@BinaryField('utf16', 0, 256)`
|
|
15
|
+
- 到: 自定义 `fromPayload` / `toPayload` 实现
|
|
16
|
+
|
|
17
|
+
2. **STOC_CHAT** (0x19)
|
|
18
|
+
- 文件: `src/protos/stoc/proto/chat.ts`
|
|
19
|
+
- 从: `@BinaryField('utf16', 2, 256)`
|
|
20
|
+
- 到: 自定义实现,先处理 `player_type`,再处理可变长度字符串
|
|
21
|
+
|
|
22
|
+
3. **CTOS_EXTERNAL_ADDRESS** (0x17)
|
|
23
|
+
- 文件: `src/protos/ctos/proto/external-address.ts`
|
|
24
|
+
- 从: `@BinaryField('utf16', 4, 256)`
|
|
25
|
+
- 到: 自定义实现,先处理 `real_ip`,再处理可变长度字符串
|
|
26
|
+
|
|
27
|
+
## 实现特性
|
|
28
|
+
|
|
29
|
+
### 序列化(toPayload)
|
|
30
|
+
```typescript
|
|
31
|
+
// 1. 将字符串转换为 UTF-16LE
|
|
32
|
+
// 2. 在末尾添加 null terminator (\0\0)
|
|
33
|
+
// 3. 只发送实际内容长度,不填充到最大长度
|
|
34
|
+
|
|
35
|
+
// 示例:"Hello" → 12 bytes (不是 512 bytes)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 反序列化(fromPayload)
|
|
39
|
+
```typescript
|
|
40
|
+
// 1. 读取所有可用字节
|
|
41
|
+
// 2. 使用 TextDecoder('utf-16le') 解码
|
|
42
|
+
// 3. 移除末尾的所有 null 字符
|
|
43
|
+
// 4. 不要求末尾必须有 \0\0
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 带宽优化
|
|
47
|
+
|
|
48
|
+
| 协议 | 固定长度 | 可变长度 (示例) | 节省 |
|
|
49
|
+
|------|----------|-----------------|------|
|
|
50
|
+
| CTOS_CHAT | 512 bytes | ~10-30 bytes | ~95% |
|
|
51
|
+
| STOC_CHAT | 514 bytes | ~10-30 bytes | ~95% |
|
|
52
|
+
| CTOS_EXTERNAL_ADDRESS | 516 bytes | ~20-50 bytes | ~90% |
|
|
53
|
+
|
|
54
|
+
**实际示例**:
|
|
55
|
+
- "Hello": 12 bytes vs 512 bytes (节省 97.7%)
|
|
56
|
+
- "GG": 8 bytes vs 512 bytes (节省 98.4%)
|
|
57
|
+
- "example.com": 28 bytes vs 516 bytes (节省 94.6%)
|
|
58
|
+
|
|
59
|
+
## 代码对比
|
|
60
|
+
|
|
61
|
+
### 之前 (固定长度)
|
|
62
|
+
```typescript
|
|
63
|
+
export class YGOProCtosChat extends YGOProCtosBase {
|
|
64
|
+
static identifier = 0x16;
|
|
65
|
+
|
|
66
|
+
@BinaryField('utf16', 0, 256)
|
|
67
|
+
msg: string;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
- 总是序列化 512 字节
|
|
71
|
+
- 浪费带宽
|
|
72
|
+
- 简单但低效
|
|
73
|
+
|
|
74
|
+
### 之后 (可变长度)
|
|
75
|
+
```typescript
|
|
76
|
+
export class YGOProCtosChat extends YGOProCtosBase {
|
|
77
|
+
static identifier = 0x16;
|
|
78
|
+
msg: string;
|
|
79
|
+
|
|
80
|
+
fromPayload(data: Uint8Array): this {
|
|
81
|
+
const decoder = new TextDecoder('utf-16le');
|
|
82
|
+
this.msg = decoder.decode(data).replace(/\0+$/, '');
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
toPayload(): Uint8Array {
|
|
87
|
+
// 编码为 UTF-16LE + null terminator
|
|
88
|
+
const utf16 = new Uint16Array(text.length + 1);
|
|
89
|
+
for (let i = 0; i < text.length; i++) {
|
|
90
|
+
utf16[i] = text.charCodeAt(i);
|
|
91
|
+
}
|
|
92
|
+
utf16[text.length] = 0; // null terminator
|
|
93
|
+
return new Uint8Array(utf16.buffer);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
- 只序列化实际内容
|
|
98
|
+
- 高效利用带宽
|
|
99
|
+
- 自定义但灵活
|
|
100
|
+
|
|
101
|
+
## 兼容性
|
|
102
|
+
|
|
103
|
+
### ✅ 向前兼容
|
|
104
|
+
- 始终添加 null terminator,确保 C/C++ 代码可以正确读取
|
|
105
|
+
- UTF-16LE 编码与原有实现一致
|
|
106
|
+
|
|
107
|
+
### ✅ 向后兼容
|
|
108
|
+
- 解析时不要求 null terminator,可以处理各种格式
|
|
109
|
+
- 自动移除末尾 null 字符,避免显示问题
|
|
110
|
+
|
|
111
|
+
### ✅ 多语言支持
|
|
112
|
+
- UTF-16LE 编码支持所有 Unicode BMP 字符
|
|
113
|
+
- 中文、日文、韩文等多语言无问题
|
|
114
|
+
|
|
115
|
+
## 测试
|
|
116
|
+
|
|
117
|
+
### 测试脚本
|
|
118
|
+
`test-chat-protocols.ts` 包含以下测试:
|
|
119
|
+
|
|
120
|
+
1. **CTOS_CHAT 测试**
|
|
121
|
+
- 序列化 "Hello World!"
|
|
122
|
+
- 验证 null terminator 存在
|
|
123
|
+
- 反序列化并验证内容
|
|
124
|
+
|
|
125
|
+
2. **STOC_CHAT 测试**
|
|
126
|
+
- 序列化 player_type + "GG!"
|
|
127
|
+
- 验证 player_type 正确编码
|
|
128
|
+
- 验证 null terminator 存在
|
|
129
|
+
- 反序列化并验证内容
|
|
130
|
+
|
|
131
|
+
3. **CTOS_EXTERNAL_ADDRESS 测试**
|
|
132
|
+
- 序列化 real_ip + "example.com"
|
|
133
|
+
- 验证 real_ip 正确编码
|
|
134
|
+
- 验证 null terminator 存在
|
|
135
|
+
- 反序列化并验证内容
|
|
136
|
+
|
|
137
|
+
4. **无 null terminator 测试**
|
|
138
|
+
- 验证可以解析不带 null terminator 的数据
|
|
139
|
+
- 确保向后兼容
|
|
140
|
+
|
|
141
|
+
### 运行测试
|
|
142
|
+
```bash
|
|
143
|
+
npx tsx test-chat-protocols.ts
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 构建结果
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
✅ 编译成功
|
|
150
|
+
✅ 无 linter 错误
|
|
151
|
+
✅ 无 TypeScript 错误
|
|
152
|
+
|
|
153
|
+
文件大小:
|
|
154
|
+
- dist/index.cjs: 154.6kb
|
|
155
|
+
- dist/index.mjs: 144.4kb
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 影响范围
|
|
159
|
+
|
|
160
|
+
### 修改的文件
|
|
161
|
+
- `src/protos/ctos/proto/chat.ts` ✏️
|
|
162
|
+
- `src/protos/stoc/proto/chat.ts` ✏️
|
|
163
|
+
- `src/protos/ctos/proto/external-address.ts` ✏️
|
|
164
|
+
|
|
165
|
+
### 新增的文件
|
|
166
|
+
- `test-chat-protocols.ts` ✨
|
|
167
|
+
- `VARIABLE_LENGTH_STRINGS.md` 📄
|
|
168
|
+
- `VARIABLE_LENGTH_UPDATE.md` 📄 (本文件)
|
|
169
|
+
|
|
170
|
+
### 更新的文件
|
|
171
|
+
- `FINAL_SUMMARY.md` 📝
|
|
172
|
+
|
|
173
|
+
## 技术细节
|
|
174
|
+
|
|
175
|
+
### UTF-16LE 编码
|
|
176
|
+
```typescript
|
|
177
|
+
// 手动编码(避免依赖 TextEncoder 的 UTF-16 支持)
|
|
178
|
+
const utf16 = new Uint16Array(text.length + 1);
|
|
179
|
+
for (let i = 0; i < text.length; i++) {
|
|
180
|
+
utf16[i] = text.charCodeAt(i);
|
|
181
|
+
}
|
|
182
|
+
utf16[text.length] = 0; // null terminator
|
|
183
|
+
const bytes = new Uint8Array(utf16.buffer);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 多字节字段编码
|
|
187
|
+
```typescript
|
|
188
|
+
// Little Endian
|
|
189
|
+
result[0] = value & 0xff;
|
|
190
|
+
result[1] = (value >> 8) & 0xff;
|
|
191
|
+
result[2] = (value >> 16) & 0xff;
|
|
192
|
+
result[3] = (value >> 24) & 0xff;
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 解码
|
|
196
|
+
```typescript
|
|
197
|
+
// 使用浏览器/Node.js 内置 API
|
|
198
|
+
const decoder = new TextDecoder('utf-16le');
|
|
199
|
+
const text = decoder.decode(data).replace(/\0+$/, '');
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 性能影响
|
|
203
|
+
|
|
204
|
+
### ✅ 优化
|
|
205
|
+
- 减少网络传输字节数(节省 90-98%)
|
|
206
|
+
- 减少内存占用
|
|
207
|
+
- 更快的序列化/反序列化(处理的字节更少)
|
|
208
|
+
|
|
209
|
+
### ⚠️ 注意
|
|
210
|
+
- 自定义实现稍微增加了代码复杂度
|
|
211
|
+
- 需要手动处理字符串编码(不使用装饰器)
|
|
212
|
+
|
|
213
|
+
## 未来改进
|
|
214
|
+
|
|
215
|
+
可能的未来改进方向:
|
|
216
|
+
|
|
217
|
+
1. 支持 UTF-8 编码(更节省带宽)
|
|
218
|
+
2. 支持代理对(Surrogate Pairs)处理 Unicode 扩展字符
|
|
219
|
+
3. 添加最大长度限制检查
|
|
220
|
+
4. 性能优化(缓存编码器)
|
|
221
|
+
|
|
222
|
+
## 总结
|
|
223
|
+
|
|
224
|
+
✅ **成功将三个协议改为可变长度实现**
|
|
225
|
+
✅ **大幅减少网络带宽使用(节省 90-98%)**
|
|
226
|
+
✅ **保持完全兼容性(向前和向后)**
|
|
227
|
+
✅ **所有测试通过,构建成功**
|
|
228
|
+
|
|
229
|
+
这次更新显著提升了这三个协议的效率,特别是在频繁发送聊天消息的场景下。
|