找回密码
 registerss
搜索
查看: 27|回复: 0

洱海月 v0-07-0300 封包解析:ID 724 GCBBSMessages(摊位留言板内容同步)

[复制链接]
  • 打卡等级:本地老炮
  • 打卡总天数:603
  • 打卡月天数:1
  • 打卡总奖励:600
  • 最近打卡:2026-10-03 06:32:28
Waylee 发表于 2026-4-13 21:58 | 显示全部楼层 |阅读模式 | Google Chrome | Windows 10

马上注册,查看网站隐藏内容!!

您需要 登录 才可以下载或查看,没有账号?registerss

×

GCBBSMessages 是当前项目里用于下发摊位留言板内容的变长下行包。它既是 CGBBSApply 读取留言板时的直接回包,也是 CGBBSSychMessages 在新增、回复、删除、改标题后触发的统一刷新包。

包信息

项目 内容
封包 ID 724
包名 GCBBSMessages
方向 GC 下行
相关请求 CGBBSApply、CGBBSSychMessages
作用 同步指定摊位的留言板版本号、标题与最多 20 条留言

包体结构

当前 Game.exe 与 Lua 都能确认,这条包是动态长度结构,实际长度由标题和每条留言正文决定。

size = 10 + title_len + sum(entry_size)

entry_size =
  47 + message_len                              // has_reply == 0
  50 + message_len + replymessage_len          // has_reply == 1

其中固定 10 字节对应:

  • m_objID:4
  • serial:4
  • title_len:1
  • num:1

头部

偏移 长度 字段 类型 含义
0x00 0x04 m_objID int32 目标摊位所属角色对象 ID
0x04 0x04 serial uint32 当前留言板版本号
0x08 0x01 title_len uint8 标题字节长度
0x09 title_len title byte[] GBK 标题内容
0x09 + title_len 0x01 num uint8 留言条数,当前客户端认可上限 20
0x0A + title_len ... messages entry[num] 留言数组

单条留言结构

设单条留言起始偏移为 entry_base。

偏移 长度 字段 类型 含义
entry_base + 0x00 0x27 author char[39] 作者名固定槽位
entry_base + 0x27 0x04 id uint32 留言 ID
entry_base + 0x2B 0x01 hour uint8 留言小时
entry_base + 0x2C 0x01 min uint8 留言分钟
entry_base + 0x2D 0x01 has_reply uint8 是否附带回复,0/1

当 has_reply == 0 时,后续结构为:

偏移 长度 字段 类型 含义
entry_base + 0x2E 0x01 message_len uint8 留言正文长度
entry_base + 0x2F message_len message byte[] GBK 留言正文

当 has_reply == 1 时,后续结构为:

偏移 长度 字段 类型 含义
entry_base + 0x2E 0x01 re_hour uint8 回复小时
entry_base + 0x2F 0x01 re_min uint8 回复分钟
entry_base + 0x30 0x01 message_len uint8 留言正文长度
entry_base + 0x31 message_len message byte[] GBK 留言正文
entry_base + 0x31 + message_len 0x01 replymessage_len uint8 回复正文长度
entry_base + 0x32 + message_len replymessage_len replymessage byte[] GBK 回复正文

补充说明:

  • title_len、message_len、replymessage_len 在协议上都是 uint8,单段文本理论上最高 255 字节。
  • 当前 Game.exe 在本地包类中为标题、正文、回复正文各预留了 300 字节缓冲,但线上主链实际长度仍以 uint8 为准。
  • Lua bis() 把 serial 读作 uint32,bos() 则用 writeint() 写出同样的 4 字节;当前版本号只做正向递增,线上行为等价。

当前实际行为

  • 当前 Game.exe 的 GetPacketID() 返回 724。
  • 当前 Game.exe 的 Read() / Write() 顺序与 Lua packet.GCBBSMessages 一致:先读写 m_objID + serial + title_len + title + num,再逐条处理留言数组。
  • 当前 Game.exe 的 GetPacketSize() 会按标题长度与每条留言是否带回复动态累加,不是固定长度包。
  • 场景服 char_bbs_apply() 会先检查目标摊位是否处于 STALL_OPEN。若未开启,不会下发本包,而是返回 GCStallError(574)。
  • 这条包始终会携带最新 serial。如果客户端请求里的 serial 与服务端当前版本一致,Lua 仍会发送 GCBBSMessages,但内容只剩:
    • m_objID
    • serial
    • title_len = 0
    • num = 0
  • 因此当前最小包长不是 8,而是 10 字节。
  • 当 serial 不一致时,Lua 会把留言板标题和留言列表完整写入协议。
  • 留言底层由 stallbbs 的 20 槽位环形缓冲维护,char_bbs_apply() 从 final_index 开始按环形遍历有效槽位,所以客户端最终拿到的是当前有效留言的“从旧到新”顺序。
  • CGBBSSychMessages 在新增留言、回复留言、删除留言、改标题后,都会构造 ba.serial = 0 再回调 char_bbs_apply(),强制下发完整 GCBBSMessages。
  • stallbbs.serial 初始为 0,stallbox 初始化默认标题“欢迎你来到某某的摊位”时会先触发一次 set_bbs_title(),所以有效摊位通常从非零版本号开始。
  • 当前 Game.exe Execute() 不只是空返回:
    • 若 m_objID 等于本地角色对象 ID,则走自身摊位留言板分支,并用 serial 与本地缓存比对;版本相同则直接结束,不重灌列表。
    • 若 m_objID 不等于本地角色对象 ID,则走查看他人摊位的留言板分支,会直接用收到的数据刷新本地列表、标题和数量。
    • 两个分支最后都会向界面层发送事件 179,字符串参数分别是 "sale" 或 "buy"。
  • Lua 里这条包的 bis() 仅用于解包辅助,且存在 if num > 0 and num < 0x14 then 的严格小于判断;如果真按 Lua 本地解包 20 条留言,不会进入循环。但当前正式主链只用它向客户端下发,不依赖这段下行包的 Lua 解包路径。

服务端落点

  • 协议定义:
    • /home/ubuntu/Game2/services/game/packet.lua
  • 请求入口:
    • /home/ubuntu/Game2/services/msgagent.lua
  • 场景处理:
    • /home/ubuntu/Game2/services/scene/scenecore.lua
  • 留言板底层:
    • /home/ubuntu/Game2/lualib/stallbox.lua

关键代码

当前 Lua 协议定义

packet.GCBBSMessages = {
    xy_id = packet.XYID_GC_BBS_MESSSAGES,
    ctor = function(self)
        self.messages = {}
    end,
    bis = function(self, buffer)
        local stream = bistream.new()
        stream:attach(buffer)
        self.m_objID = stream:readint()
        self.serial = stream:readuint()
        local len = stream:readuchar()
        self.title = gbk.toutf8(stream:read(len))
        local num = stream:readuchar()
        if num > 0 and num < 0x14 then
            for i = 1, num do
                local message = {}
                message.author = gbk.toutf8(stream:read(0x27))
                message.id = stream:readuint()
                message.hour = stream:readuchar()
                message.min = stream:readuchar()
                message.has_reply = stream:readuchar()
                if message.has_reply == 1 then
                    message.re_hour = stream:readuchar()
                    message.re_min = stream:readuchar()
                    local len = stream:readuchar()
                    message.message = stream:read(len)
                    len = stream:readuchar()
                    message.replymessage = stream:read(len)
                else
                    local len = stream:readuchar()
                    message.message = stream:read(len)
                end
                table.insert(self.messages, message)
            end
        end
    end,
    bos = function(self)
        local stream = bostream.new()
        stream:writeint(self.m_objID)
        stream:writeint(self.serial)
        self.title = gbk.fromutf8(self.title or "")
        local len = string.len(self.title)
        stream:writeuchar(len)
        stream:write(self.title, len)
        local num = #self.messages
        stream:writeuchar(num)
        ...
    end
}

当前 Lua 场景发送

function scenecore:char_bbs_apply(who, ba)
    local tar = self.objs[ba.m_objID]
    local stall_box = tar:get_stall_box()
    if stall_box:get_stall_status() ~= stall_box.STALL_STATUS.STALL_OPEN then
        local msg = packet_def.GCStallError.new()
        msg.result = msg.STALL_MSG.ERR_ILLEGAL
        self:send2client(who, msg)
        return
    end

    local msg = packet_def.GCBBSMessages.new()
    local bbs = stall_box:get_bbs()
    msg.m_objID = tar:get_obj_id()
    msg.serial = bbs:get_serial()
    if ba.serial == bbs:get_serial() then
    else
        local cur_index = bbs:get_final_index()
        for i = 0, bbs.MAX_BBS_MESSAGE_NUM - 1 do
            local entry = bbs:get_message_by_index(cur_index)
            if entry then
                table.insert(msg.messages, {
                    id = entry.id,
                    author = entry.author,
                    hour = entry.hour,
                    min = entry.min,
                    message = entry.message,
                    item_transfer = entry.item_transfer,
                    has_reply = entry.has_reply,
                    re_hour = entry.re_hour,
                    re_min = entry.re_min,
                    replymessage = entry.replymessage,
                })
            end
            cur_index = cur_index + 1
            if cur_index == bbs.MAX_BBS_MESSAGE_NUM then
                cur_index = 0
            end
        end
        msg.title = bbs:get_bbs_title()
    end
    self:send2client(who, msg)
end

当前 Lua 留言板底层

stallbbs.MAX_BBS_MESSAGE_NUM = 20

function stallbbs:add_new_message(message_entry)
    message_entry.has_reply = false
    self.message_data[self.final_index] = message_entry
    self.is_vaild[self.final_index] = true
    self:inc_serial()
    if self.final_index >= self.MAX_BBS_MESSAGE_NUM - 1 then
        self.final_index = 0
    else
        self.final_index = self.final_index + 1
    end
    return true
end

function stallbbs:set_bbs_title(title)
    self.title = title or ""
    self:inc_serial()
    return true
end

function stallbbs:get_final_index()
    return self.final_index
end

当前 Game.exe 结论

GetPacketID() = 724;

GetPacketSize() =
  10 + title_len
  + sum(has_reply ? 50 + message_len + reply_len
                  : 47 + message_len);

Read/Write() {
    i32  m_objID;
    u32  serial;
    u8   title_len;
    char title[title_len];
    u8   num;
    repeat num times {
        char author[39];
        u32  id;
        u8   hour;
        u8   min;
        u8   has_reply;
        if (has_reply) {
            u8 re_hour;
            u8 re_min;
            u8 message_len;
            char message[message_len];
            u8 reply_len;
            char reply[reply_len];
        } else {
            u8 message_len;
            char message[message_len];
        }
    }
}

Execute() {
    if (m_objID == local_obj_id) {
        if (serial != sale_bbs.serial) {
            refresh_sale_bbs(title, messages, num, serial);
        }
        notify_ui(179, "sale");
    } else {
        refresh_buy_bbs(title, messages, num, serial);
        notify_ui(179, "buy");
    }
    return 2;
}

结论

  1. GCBBSMessages(724) 是当前项目里真实在用的摊位留言板下行同步包,对应 CGBBSApply 与 CGBBSSychMessages 两条上游请求链路。
  2. 它不是固定长度包,长度公式应写成 10 + title_len + Σ(entry_size),最小包长是 10 字节。
  3. 单条留言固定包含 author[39] + id + hour + min + has_reply,正文与回复正文都用 uint8 长度前缀描述。
  4. 服务端实际最多编码 20 条留言,返回顺序是当前有效留言的“从旧到新”。
  5. serial 命中时,Lua 仍会发送 GCBBSMessages,但内容是空标题与 0 条留言;当前 Game.exe 只在“自身摊位”分支上用 serial 做短路。
  6. Lua 的本地 bis() 辅助对 num == 20 存在严格小于判断,但这不影响当前服务端主链的对外下发行为。
您需要登录后才可以回帖 登录 | registerss

本版积分规则

QQ|手机版|雪舞知识库 ( 浙ICP备15015590号-1 | 萌ICP备20232229号|浙公网安备33048102000118号 )|天天打卡

GMT+8, 2026-10-4 11:26 , Processed in 0.076059 second(s), 26 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表