fix(net): 修复 40250 进入游戏时的收包错位

- GC_ITEM_DEL(20) 按 40250 旧结构为 42 字节,GC_REFINE_INFORMATION(95) 为 59 字节
- 公会标记连接接受 GC_MARK_DIFF_DATA(101),按 1 字节消费
- GUILD_SUBHEADER_GC_SKILL_INFO 服务器声明 22 字节但实际只写 21 字节,
  按实际长度分帧,修复 "unknown GC header 200 (last: 75,20)" 断线
- 会话测试的 quest info 包改为 40250 按 flag 定长的格式,新增对应分帧测试

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
This commit is contained in:
shenlei
2026-09-12 21:29:21 +09:00
co-authored by Claude Opus 5
parent 474a709f69
commit 85ce75e707
7 changed files with 239 additions and 23 deletions
@@ -5,6 +5,7 @@
#include <cstdio>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
using namespace mtnet::classic;
@@ -248,6 +249,46 @@ int main() {
CHECK(s.outgoing_pending() == 0, "unknown header does not resync into trailing bytes");
}
// ------------------------------------------- GC_GUILD SKILL_INFO short write
{
// guild.cpp CGuild::SendSkillInfoPacket: size = 4 + 6 + GUILD_SKILL_COUNT (22)
// but only skill_point + abySkill[12] + power(2) + max_power(2) follow the
// head (21 bytes). LoginMember sends it right before SendEnemyGuild's
// TPacketGCGuildName (size 20 = 0x14), which is how a real login desynced
// into "unknown GC header 200 (last: 75,20)".
ClassicStream s;
std::string err;
s.on_error = [&](const std::string &e) { err = e; };
std::vector<std::pair<uint8_t, std::vector<uint8_t>>> got;
s.on_packet = [&](uint8_t h, const uint8_t *body, uint32_t len) {
got.push_back({h, std::vector<uint8_t>(body, body + len)});
return true;
};
std::vector<uint8_t> skill = {HDR_GC_GUILD, 22, 0, 12, 3};
for (uint8_t i = 0; i < 12; ++i) {
skill.push_back(static_cast<uint8_t>(i + 1));
}
skill.insert(skill.end(), {0x10, 0x00, 0x20, 0x00}); // power 16, max_power 32
CHECK(skill.size() == 21, "server SKILL_INFO writes 21 bytes");
std::vector<uint8_t> name = {HDR_GC_GUILD, 20, 0, 16, 0x2A, 0, 0, 0};
const char gname[12] = "Wolves";
name.insert(name.end(), gname, gname + sizeof(gname));
CHECK(name.size() == 20, "TPacketGCGuildName is 20 bytes");
std::vector<uint8_t> wire = skill;
wire.insert(wire.end(), name.begin(), name.end());
s.feed(wire.data(), wire.size());
CHECK(err.empty(), "guild skill info + guild name: no framing error");
CHECK(got.size() == 2, "guild skill info + guild name: two packets delivered");
if (got.size() == 2) {
CHECK(got[0].second.size() == 1 + 17 && got[0].second[0] == 12 &&
got[0].second[1] == 3 && got[0].second[16] == 0x20,
"skill info body = subheader + 17 bytes");
CHECK(got[1].first == HDR_GC_GUILD && got[1].second.size() == 17 &&
got[1].second[0] == 16 && got[1].second[1] == 0x2A,
"guild name packet framed intact after skill info");
}
}
// ---------------------------------------------------------- rejected handler
{
ClassicStream s;