CGAskMail 是旧版客户端用于查询邮件状态与拉取邮件内容的请求包。当前 Temp_Release_v0-07-0300 的 Lua 服务端已经真实接入这条协议,请求入口位于 /home/ubuntu/Game2/services/msgagent.lua 的 request:CGAskMail();包体虽然只有 1 个字节,但 m_askType 会直接决定这是“登录检查邮件数量”还是“拉取邮件正文”。
当前实现里,CGAskMail 不是空包,而是一个明确的 uint8 请求。
包信息
| 项目 |
内容 |
| 封包 ID |
386 |
| 包名 |
CGAskMail |
| 方向 |
CG 客户端 -> 游戏服 |
| 固定包长 |
1 |
| 请求字段 |
m_askType:uint8 |
| 直接回包 |
GCNotifyMail / GCMail |
| 回包 ID |
633 / 273 |
| 回包包长 |
1 / 动态 |
| 当前 Lua 协议定义 |
/home/ubuntu/Game2/services/game/packet.lua |
| 当前 Lua 请求入口 |
/home/ubuntu/Game2/services/msgagent.lua |
| 当前邮件枚举定义 |
/home/ubuntu/Game2/lualib/define.lua |
| 主要作用 |
登录时同步邮件数量,或主动拉取邮件正文 |
当前 Game.exe 的虚函数结论为:
CGAskMail
GetPacketID() = 386;
GetPacketSize() = 1;
Read = 1 byte at (this + 24); // m_askType
Write = 1 byte at (this + 24); // m_askType
Execute thunk = 0x1405C7E80 -> sub_140328980;
GCNotifyMail
GetPacketID() = 633;
GetPacketSize() = 1;
GCMail
GetPacketID() = 273;
GetPacketSize() = dynamic;
包体结构
CGAskMail 是固定 1 字节请求包。
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
m_askType |
uint8 |
邮件查询模式;0 表示登录检查邮件数量,1 表示请求邮件正文 |
当前枚举定义如下:
define.ASK_TYPE =
{
ASK_TYPE_LOGIN = 0,
ASK_TYPE_MAIL = 1,
}
当前实际行为 / 模式说明
m_askType = 0 对应 ASK_TYPE_LOGIN:
- 服务端调用
get_db_mail_list() 统计当前角色可见邮件数。
- 返回
GCNotifyMail,只同步 m_MailCount,不下发正文。
- 客户端侧
InfoBrowser.lua、MainMenuBar.lua 会通过 DataPool:GetMailNumber() 驱动邮件提示与界面刷新。
m_askType = 1 对应 ASK_TYPE_MAIL:
- 服务端构造
GCMail。
- 当前 Lua 主链每次只把
mail_list[1] 放进回包。
- 发送前会立即执行
delete_mail(mail_list[1]),把这封邮件从角色数据库记录里删除。
count 当前只会是 0 或 1。
leave_count 表示本次未下发、仍留在列表中的剩余邮件数。
get_db_mail_list() 会先过滤脚本邮件:
MAIL_TYPE_SCRIPT = 2 的邮件不会原样发给客户端。
- 当前实现会先调用
execute_script_mail(mail),并同步删除该邮件。
- 真正进入
GCNotifyMail / GCMail 的,是普通邮件、系统邮件等可展示邮件。
- 当前
Game.exe 的 GCMail::Execute 还能确认一个显示行为:
- 当邮件
flag == 1,客户端会按系统邮件处理,发件人显示会替换成 SYSTEM_MAIL。
关联回包结构
GCNotifyMail
GCNotifyMail 是固定 1 字节回包,只用于同步当前邮件数量。
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
m_MailCount |
uint8 |
当前可见邮件数量 |
GCMail
GCMail 是变长回包。协议层最多可写 0x14 封邮件,但当前 Lua 主链每次只下发 0 或 1 封。
GCMail 包头与尾部
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
count |
uint8 |
本次实际下发的邮件数量;当前实现只会是 0 或 1 |
变长尾部 |
0x01 |
leave_count |
uint8 |
本次未下发、仍剩余的邮件数量 |
单封邮件结构
以下偏移以“单封邮件条目起始位置”为基准;由于 source、dest、content 都是变长字符串,后续字段偏移随长度动态变化。
| 偏移 |
长度 |
字段 |
类型 |
含义 |
+0x00 |
0x04 |
guid |
int32 |
邮件标识 |
+0x04 |
0x01 |
source_len |
uint8 |
发件人字符串长度 |
+0x05 |
source_len |
source |
char[n] |
发件人显示名 |
+0x05 + source_len |
0x04 |
portrait_id |
int32 |
发件人头像 ID |
+0x09 + source_len |
0x04 |
related_guid |
int32 |
当前 Lua 写包时与前一个 guid 相同;现阶段可视为重复写出的邮件关联标识,语义不宜过度定死 |
+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 |
flag |
uint32 |
邮件类型 / 标志 |
+0x14 + source_len + dest_len + content_len |
0x04 |
create_time |
uint32 |
创建时间 |
条件追加 |
0x14 |
param_1 ~ param_5 |
int32[5] |
仅在 flag == 2 时额外写出 |
当前邮件类型定义如下:
define.MAIL_TYPE =
{
MAIL_TYPE_NORMAL = 0,
MAIL_TYPE_SYSTEM = 1,
MAIL_TYPE_SCRIPT = 2,
MAIL_TYPE_NORMAL2 = 3,
}
需要注意的是,GCMail 写包层虽然保留了 flag == 2 时附带 param_1 ~ param_5 的分支,但当前 get_db_mail_list() 会先执行并删除脚本邮件,因此当前主链通常不会把这段脚本邮件结构真正发给客户端。
服务端落点
这条包在当前项目中的关键位置如下:
- 协议定义:
/home/ubuntu/Game2/services/game/packet.lua
- 请求入口 / 邮件主逻辑:
/home/ubuntu/Game2/services/msgagent.lua
- 邮件与请求类型枚举:
/home/ubuntu/Game2/lualib/define.lua
- 客户端邮件数量提示界面:
客户端/Interface/InfoBrowser/InfoBrowser.lua
客户端/Interface/MainMenuBar/MainMenuBar.lua
关键代码
当前 Lua 协议定义
packet.CGAskMail = {
xy_id = packet.XYID_CG_ASK_MAIL,
new = function(o)
o = o or {}
setmetatable(o, {__index = packet.CGAskMail})
o:ctor()
return o
end,
ctor = function(self) self.m_askType = 0 end,
bis = function(self, buffer)
local stream = bistream.new()
stream:attach(buffer)
self.m_askType = stream:readuchar()
end
}
当前 Lua 请求处理
function request:CGAskMail()
local mail_list = get_db_mail_list()
if self.m_askType == define.ASK_TYPE.ASK_TYPE_LOGIN then
local msg = packet_def.GCNotifyMail.new()
msg.m_MailCount = #mail_list
Net:send(msg)
else
local msg = packet_def.GCMail.new()
if #mail_list > 0 then
table.insert(msg.mails, mail_list[1])
delete_mail(mail_list[1])
msg.count = 1
else
msg.count = 0
end
msg.leave_count = #mail_list - #msg.mails
Net:send(msg)
end
end
当前 Lua 邮件来源过滤
local function get_db_mail_list()
local response = skynet.call(".char_db", "lua", "findOne", ...)
local mail_list = response.mail_list or {}
local common_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
当前 Game.exe 读写结论
CGAskMail 在当前客户端里的结构非常直接:
Read :
read(this + 24, 1); // m_askType
Write:
write(this + 24, 1); // m_askType
GetPacketSize() = 1;
GCMail 的当前 Lua 写包顺序则可以概括为:
writeuchar(count);
repeat each mail:
writeint(guid);
writeuchar(source_len);
write(source, source_len);
writeint(portrait_id);
writeint(related_guid);
writeuchar(dest_len);
write(dest, dest_len);
writeushort(content_len);
write(content, content_len);
writeuint(flag);
writeuint(create_time);
if (flag == 2) write 5 * int32 params;
writeuchar(leave_count);
结论
CGAskMail 在 Temp_Release_v0-07-0300 里已经可以明确收敛为一个“邮件查询模式请求包”:
- 它不是空包,而是固定 1 字节
m_askType。
ASK_TYPE_LOGIN = 0 时,只回 GCNotifyMail 同步邮件数量。
ASK_TYPE_MAIL = 1 时,回 GCMail 拉取邮件正文。
- 当前 Lua 主链每次只下发 1 封普通/系统邮件,并在发送前立即删除该邮件记录。
leave_count 用于表示尚未继续下发的剩余邮件数量。
因此,这条协议的核心重点不在复杂包体,而在 m_askType 控制出的两种工作模式,以及当前 Lua 邮件队列“单封取出、立即删除、剩余数量继续轮询”的实际行为。