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

洱海月 v0-07-0300 封包解析:ID 414 CGMail(玩家邮件发送请求)

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

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

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

×

CGMail 是旧版客户端里用于发送玩家邮件的请求包。当前 Game.exe 可以确认它的变长结构与长度计算方式,当前 Lua 服务端则已经接入了完整的收信、发信、脚本邮件执行链。

包信息

项目 内容
封包 ID 414
包名 CGMail
相关回包 GCNotifyMail、GCMail、GCRetSendMail
相关回包 ID 633、273、329
作用 向服务端提交一封邮件,携带发件人信息、收件人名字、正文、邮件类型以及可选脚本参数

包体结构

当前 Game.exe 与 Lua 协议定义都能确认,CGMail 是可变长度结构,长度上限受三段字符串限制:

  • source_len <= 30
  • dest_len <= 30
  • content_len <= 256

主结构如下:

偏移 长度 字段 类型 含义
0x00 0x04 sender_guid int32 发件人 GUID;当前 Lua 会强校验它必须等于当前角色 GUID
0x04 0x01 source_len uint8 发件人名字长度
0x05 source_len source char[n] 发件人名字
0x05 + source_len 0x04 portrait_id int32 发件人头像 ID
0x09 + source_len 0x04 extra_guid int32 附加整型字段,当前 Lua 主链未直接使用
0x0D + source_len 0x01 dest_len uint8 收件人名字长度
0x0E + source_len dest_len dest char[n] 收件人名字
0x0E + source_len + dest_len 0x02 content_len uint16 邮件正文长度
0x10 + source_len + dest_len content_len content char[n] 邮件正文
0x10 + source_len + dest_len + content_len 0x04 mail_type uint32 邮件类型:普通 / 系统 / 脚本邮件
0x14 + source_len + dest_len + content_len 0x04 create_time uint32 邮件时间戳

当 mail_type == 2 时,还会附带 5 个脚本参数:

偏移 长度 字段 类型 含义
0x18 + source_len + dest_len + content_len 0x04 script_param_1 int32 脚本邮件参数 1
0x1C + source_len + dest_len + content_len 0x04 script_param_2 int32 脚本邮件参数 2
0x20 + source_len + dest_len + content_len 0x04 script_param_3 int32 脚本邮件参数 3
0x24 + source_len + dest_len + content_len 0x04 script_param_4 int32 脚本邮件参数 4
0x28 + source_len + dest_len + content_len 0x04 script_param_5 int32 脚本邮件参数 5

包尾还有 1 个额外整型字段:

偏移 长度 字段 类型 含义
0x18 + source_len + dest_len + content_len 0x04 client_tag uint32 当 mail_type != 2 时出现的尾字段,当前 Lua 主链未直接使用
0x2C + source_len + dest_len + content_len 0x04 client_tag uint32 当 mail_type == 2 时出现的尾字段,当前 Lua 主链未直接使用

当前 Game.exe 的长度计算方式可整理为:

mail_type != 2: source_len + dest_len + content_len + 28
mail_type == 2: source_len + dest_len + content_len + 48

当前实际行为 / 模式说明

  • 当前 Game.exe 中,CGMail::GetPacketID() 返回 414。
  • 当前 Game.exe 的 Read() / Write() 顺序与当前 Lua packet.CGMail.bis() 一致:
    • sender_guid
    • source_len + source
    • portrait_id
    • extra_guid
    • dest_len + dest
    • content_len + content
    • mail_type
    • create_time
    • script_param_1..5(仅 mail_type == 2)
    • client_tag
  • 当前 Game.exe 的 Execute() 对客户端侧是空处理,直接返回 2。
  • 当前 Lua 请求入口 request:CGMail() 只做一条硬校验:
    • assert(self.guid == my_guid, self.guid)
  • 校验通过后,请求会直接转发到世界服:
    • skynet.call(".world", "lua", "send_mail", self)
  • 当前世界服 worldcore:send_mail() 的实际处理流程是:
    • 空正文直接拒绝
    • 按 dest 查找角色名字
    • 找到目标角色后把整封邮件写入角色 mail_list
    • 目标在线时再通知其 agent 触发 new_mail
  • 当前邮件收取链路是:
    • CGAskMail(386) 请求邮箱
    • 登录态或新邮件提示走 GCNotifyMail(633)
    • 具体邮件内容走 GCMail(273)
  • 当前 Lua 还把 mail_type == 2 视为脚本邮件:
    • 这类邮件不会进入普通收件箱列表
    • 会在 msgagent:get_db_mail_list() 中被提前取出
    • 随后调用场景服 execute_script_mail
    • 最终落到 scripts/event/mail.lua 的 ExecuteMail
  • 当前 Game.exe 还存在 GCRetSendMail(329) 客户端处理包,已确认 1 字节结果码分支包括:
    • 0:对方不是正式好友
    • 1:你不是对方的正式好友
    • 2:对方不在线
    • 3:输入内容包含敏感字词
  • 当前 Lua worldcore:send_mail() 现状未见显式发送 GCRetSendMail 的逻辑,主链只做名字存在与正文非空检查。

服务端落点

  • 协议定义:
    • /home/ubuntu/Game2/services/game/packet.lua
  • 请求入口:
    • /home/ubuntu/Game2/services/msgagent.lua
  • 世界服发信处理:
    • /home/ubuntu/Game2/services/world/worldcore.lua
  • 脚本邮件执行:
    • /home/ubuntu/Game2/services/scene/scenecore.lua
    • /home/ubuntu/Game2/services/scripts/event/mail.lua

关键代码

当前 Lua 协议定义

packet.CGMail = {
    xy_id = packet.XYID_CG_MAIL,
    bis = function(self, buffer)
        local stream = bistream.new()
        stream:attach(buffer)
        self.guid = stream:readint()
        local source_size = stream:readuchar()
        source_size = source_size >= 0x1E and 0x1E or source_size
        self.source = gbk.toutf8(stream:read(source_size))
        self.portrait_id = stream:readint()
        self.unknow_2 = stream:readint()
        local dest_size = stream:readuchar()
        dest_size = dest_size >= 0x1E and 0x1E or dest_size
        self.dest = gbk.toutf8(stream:read(dest_size))
        local content_size = stream:readushort()
        content_size = content_size >= 0x100 and 0x100 or content_size
        self.content = gbk.toutf8(stream:read(content_size))
        self.flag = stream:readuint()
        self.create_time = stream:readuint()
        if self.flag == 2 then
            self.param_1 = stream:readint()
            self.param_2 = stream:readint()
            self.param_3 = stream:readint()
            self.param_4 = stream:readint()
            self.param_5 = stream:readint()
        end
        self.unknow_9 = stream:readuint()
    end
}

当前 Lua 请求入口与世界服处理

function request:CGMail()
    local my_guid = ma_func:get_guid()
    assert(self.guid == my_guid, self.guid)
    skynet.call(".world", "lua", "send_mail", self)
end
function worldcore:send_mail(mail)
    if trim_notice_text(mail and mail.content) == "" then
        skynet.loge("worldcore:send_mail skip empty content dest =", tostring(mail and mail.dest))
        return
    end
    local dest = mail.dest
    local user = self:get_db_user_by_name(mail.dest)
    if user then
        self:insert_mail(user.attrib.guid, mail)
        local online_user = self:find_user_by_name(dest)
        if online_user then
            skynet.send(online_user.uinfo.agent, "lua", "new_mail")
        end
    else
        print("can not find user name =", mail.dest)
    end
end

当前 Lua 脚本邮件处理

local function get_db_mail_list()
    ...
    for i = #mail_list, 1, -1 do
        local mail = mail_list[i]
        if mail.flag == define.MAIL_TYPE.MAIL_TYPE_SCRIPT then
            execute_script_mail(mail)
        else
            table.insert(common_mail_list, mail)
        end
    end
    return common_mail_list
end
function scenecore:execute_script_mail(who, mail)
    self.scriptenginer:call(define.MAIL_SCRIPT_ID, "ExecuteMail", who, mail.param_1, mail.param_2, mail.param_3, mail.param_4, mail.param_5)
end

当前 Game.exe 的读写与执行结论

GetPacketID() = 414;

GetPacketSize() {
    if (mail_type == 2) {
        return source_len + dest_len + content_len + 48;
    }
    return source_len + dest_len + content_len + 28;
}

Read(stream) {
    read_i32(sender_guid);
    read_u8(source_len);
    read_bytes(source, source_len);
    read_i32(portrait_id);
    read_i32(extra_guid);
    read_u8(dest_len);
    read_bytes(dest, dest_len);
    read_u16(content_len);
    read_bytes(content, content_len);
    read_u32(mail_type);
    read_u32(create_time);
    if (mail_type == 2) {
        read_i32(script_param_1);
        read_i32(script_param_2);
        read_i32(script_param_3);
        read_i32(script_param_4);
        read_i32(script_param_5);
    }
    read_u32(client_tag);
}

Write(stream) {
    write_i32(sender_guid);
    write_u8(source_len);
    write_bytes(source, source_len);
    write_i32(portrait_id);
    write_i32(extra_guid);
    write_u8(dest_len);
    write_bytes(dest, dest_len);
    write_u16(content_len);
    write_bytes(content, content_len);
    write_u32(mail_type);
    write_u32(create_time);
    if (mail_type == 2) {
        write_i32(script_param_1);
        write_i32(script_param_2);
        write_i32(script_param_3);
        write_i32(script_param_4);
        write_i32(script_param_5);
    }
    write_u32(client_tag);
}

Execute() {
    return 2;
}

结论

这条包在当前旧版项目里要记住七点:

  1. CGMail 的封包 ID 是 414。
  2. 它是标准变长邮件发送包,主体由 sender_guid + source + portrait_id + extra_guid + dest + content + mail_type + create_time 组成。
  3. 当前 Game.exe 与 Lua 都确认三段字符串长度限制分别是:30 / 30 / 256。
  4. 当 mail_type == 2 时,这条包会额外携带 5 个脚本参数,并走脚本邮件执行链。
  5. 当前 Lua 请求入口会先硬校验 sender_guid 是否等于当前角色 GUID,再转发到世界服。
  6. 当前世界服发信逻辑的核心条件是“正文非空、收件人名字存在”,成功后写库并通知在线玩家。
  7. 当前客户端虽然保留了 GCRetSendMail(329) 失败提示处理,但当前 Lua 主链未见显式发送该回包的实现。
您需要登录后才可以回帖 登录 | registerss

本版积分规则

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

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

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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