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

洱海月 v0-07-0300 封包解析:ID 517 CGPlayerShopFavorite(玩家商店收藏切换请求)

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

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

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

×

CGPlayerShopFavorite 是玩家商店系统里用于“添加收藏 / 取消收藏”的客户端请求包。当前 Game.exe 与现役 /home/ubuntu/Game2 Lua 都能确认:它是一条固定 13 字节请求,包体结构为 opt:uint8 + PlayerShopGUID(12)。现役服务端收到后不会直接修改商店对象,而是只在角色自身的收藏 GUID 列表里增删目标店铺,并在成功后连续回 GCPlayerShopUpdateFavorite(288) 与 GCPlayerShopAcquireShopList(578)。

包信息

项目 内容
封包 ID 517
包名 CGPlayerShopFavorite
方向 CG 客户端 -> 游戏服
固定包长 13
专用回包 GCPlayerShopUpdateFavorite(288)
相关刷新包 GCPlayerShopAcquireShopList(578)
失败提示包 GCPlayerShopError(207)
当前 Lua 状态 现役协议;已接入并实际处理
作用 切换指定玩家商店 GUID 的收藏状态

当前 Game.exe 可直接确认:

CGPlayerShopFavorite
GetPacketID()   = 0x1402CC9C0 -> 517;
GetPacketSize() = 0x1401CBFF0 -> 13;
Read            = 0x1405CB2D0;
Write           = 0x1405CB320;
Execute thunk   = 0x1405CB2C0 -> 0x140328E50;

包体结构

当前 Game.exe 与现役 Lua 协议定义一致,包体固定为 1 + 12 字节:

偏移 长度 字段 类型 含义
0x00 0x01 opt uint8 收藏操作码;当前场景服实际识别 1=添加收藏、2=取消收藏
0x01 0x02 favorite_id.world uint16 玩家商店 GUID 的 world 字段
0x03 0x04 favorite_id.server uint32 玩家商店 GUID 的 server 字段
0x07 0x04 favorite_id.scene uint32 玩家商店 GUID 的 scene 字段
0x0B 0x02 favorite_id.pollpos uint16 玩家商店 GUID 的 pollpos 字段

其中 favorite_id 在当前 Lua 中已明确对应 PlayerShopGUID:

world:uint16 + server:uint32 + scene:uint32 + pollpos:uint16

总长度固定为:

13

当前实际行为

  • 请求入口在 /home/ubuntu/Game2/services/msgagent.lua 的 request:CGPlayerShopFavorite(),随后直接转发到场景服 char_player_shop_favorite。
  • 场景服先把 favorite_id 克隆为普通表,再检查是否为空 GUID;如果为空,当前实现直接 return,不会回任何错误包。
  • 当前场景服只识别两个操作码:
    • opt == 1:添加收藏
    • opt == 2:取消收藏
  • opt 取其他值时,当前实现不会执行任何逻辑,也不会回包。
  • 添加收藏时,调用的是角色对象的 add_favorite_shop_guid():
    • 非空 GUID 才允许进入;
    • 如果该店铺已经在收藏列表中,当前实现会直接返回成功;
    • 收藏上限固定为 10,达到上限后返回失败;
    • 成功后会连续发送 GCPlayerShopUpdateFavorite(is_in_favorite = 1) 和 GCPlayerShopAcquireShopList。
  • 取消收藏时,调用的是 del_favorite_shop_guid():
    • 空 GUID 会失败,但这条链路前面已经被场景服提前拦掉;
    • 即使目标 GUID 原本不在收藏列表里,当前实现也会返回成功;
    • 因此现役 ERR_SHOP_DEL_FROM_FAVORITE(31) 在这条主链上基本不会实际触发;
    • 当前删除成功后同样会连续发送 GCPlayerShopUpdateFavorite(is_in_favorite = 0) 和 GCPlayerShopAcquireShopList。
  • 添加/删除收藏时,场景服不会先校验该店铺 GUID 当前是否真实存在于玩家商店池中。
  • 但在随后的 GCPlayerShopAcquireShopList 刷新链里,playershopmanager:get_shop_infos({ human = human }) 会先调用 human:update_favorite_shop_guids(valid_shop_guids),把已经失效或不在已挂牌商店列表中的收藏 GUID 清理掉。
  • 这意味着:当前客户端即使对一个不存在或已下架的店铺 GUID 发起收藏,请求链路也可能先收到一次“收藏状态更新成功”,随后在商店列表刷新时又被服务端静默清理。
  • 当前 Game.exe 的 Execute() 仅返回 2,没有额外本地执行逻辑。
  • 客户端侧还能看到一条字符串为 LUA:PlayerShop::AddFavor[2] param parameter error 的发送入口;结合构包逻辑可推断,当前客户端本地存在一条按店铺索引发起收藏切换的 Lua 导出接口,并会根据本地当前收藏状态自动决定本次 opt 是 1 还是 2。

服务端落点

  • 协议定义:/home/ubuntu/Game2/services/game/packet.lua
  • 请求入口:/home/ubuntu/Game2/services/msgagent.lua
  • 场景处理:/home/ubuntu/Game2/services/scene/scenecore.lua
  • 收藏列表维护:/home/ubuntu/Game2/services/scene/obj/human.lua
  • 商店列表刷新与失效收藏清理:/home/ubuntu/Game2/lualib/playershopmanager.lua

关键代码

以下代码块中,将 Game.exe 的底层流读写函数按语义简化记为 read_bytes / write_bytes,便于对照结构理解。

当前 Lua 协议定义

packet.CGPlayerShopFavorite = {
    xy_id = packet.XYID_CG_PLAYER_SHOP_FAVORITE,
    new = function()
        local o = {}
        setmetatable(o, { __index = packet.CGPlayerShopFavorite })
        o:ctor()
        return o
    end,
    ctor = function(self)
    end,
    bis = function(self, buffer)
        local stream = bistream.new()
        stream:attach(buffer)
        self.opt = stream:readuchar()
        self.favorite_id = packet.PlayerShopGUID.new()
        self.favorite_id:bis(stream)
    end
}

当前场景处理逻辑

function scenecore:char_player_shop_favorite(who, psf)
    local obj_me = self.objs[who]
    if not obj_me then
        return
    end

    local favorite_id = clone_player_shop_guid(psf.favorite_id)
    if is_null_player_shop_guid(favorite_id) then
        return
    end

    local opt = math.floor(tonumber(psf.opt) or 0)
    if opt == 1 then
        if obj_me:add_favorite_shop_guid(favorite_id) then
            send_player_shop_update_favorite(self, obj_me, favorite_id, true)
            send_player_shop_acquire_shop_list(self, obj_me)
        else
            send_player_shop_error(self, obj_me, define.PLAYER_SHOP_ERR.ERR_SHOP_ADD_TO_FAVORITE)
        end
    elseif opt == 2 then
        if obj_me:del_favorite_shop_guid(favorite_id) then
            send_player_shop_update_favorite(self, obj_me, favorite_id, false)
            send_player_shop_acquire_shop_list(self, obj_me)
        else
            send_player_shop_error(self, obj_me, define.PLAYER_SHOP_ERR.ERR_SHOP_DEL_FROM_FAVORITE)
        end
    end
end

当前角色收藏列表维护

local MAX_FAVORITE_SHOP_COUNT = 10

function human:add_favorite_shop_guid(sg)
    if is_null_shop_guid(sg) then
        return false
    end
    if self:is_in_favorite_shop(sg) then
        return true
    end
    local favorite_shop_guids = self:get_favorite_shop_guids()
    if #favorite_shop_guids >= MAX_FAVORITE_SHOP_COUNT then
        return false
    end
    table.insert(favorite_shop_guids, clone_shop_guid_value(sg))
    return true
end

function human:del_favorite_shop_guid(sg)
    if is_null_shop_guid(sg) then
        return false
    end
    local favorite_shop_guids = self:get_favorite_shop_guids()
    for i = #favorite_shop_guids, 1, -1 do
        if is_same_shop_guid(favorite_shop_guids[i], sg) then
            table.remove(favorite_shop_guids, i)
            return true
        end
    end
    return true
end

当前列表刷新时的失效收藏清理

function playershopmanager:get_shop_infos(opts)
    opts = opts or {}
    local human = opts.human
    if human then
        local valid_shop_guids = {}
        for _, shop in ipairs(self.player_shops) do
            if is_listed_player_shop(shop) then
                table.insert(valid_shop_guids, shop:get_guid())
            end
        end
        human:update_favorite_shop_guids(valid_shop_guids)
    end
    ...
end

当前 Game.exe 读写与包长

__int64 __fastcall CGPlayerShopFavorite_Read(__int64 packet, __int64 stream)
{
    read_bytes(stream, packet + 24, 1);   // opt
    read_bytes(stream, packet + 28, 12);  // favorite_id raw bytes
    return 1;
}

__int64 __fastcall CGPlayerShopFavorite_Write(__int64 packet, __int64 stream)
{
    write_bytes(stream, packet + 24, 1);
    write_bytes(stream, packet + 28, 12);
    return 1;
}

__int64 CGPlayerShopFavorite_GetPacketID()
{
    return 517;
}

__int64 CGPlayerShopFavorite_GetPacketSize()
{
    return 13;
}

__int64 CGPlayerShopFavorite_Execute()
{
    return 2;
}

结论

  1. CGPlayerShopFavorite(517) 是玩家商店收藏切换请求包,当前版本结构已经可以明确命名为 opt + favorite_id(PlayerShopGUID),固定长度 13 字节。
  2. 当前服务端只识别 opt = 1 和 opt = 2 两种操作,分别表示添加收藏和取消收藏。
  3. 成功路径下,现役服务端会连续发送 GCPlayerShopUpdateFavorite(288) 和 GCPlayerShopAcquireShopList(578);添加失败时才会发送 GCPlayerShopError(207),对应错误码 30。
  4. 当前实现有两个值得明确记录的现状:重复添加收藏会被当作成功处理;删除分支即使目标本来不在收藏列表里也会返回成功,因此错误码 31 在现役主链中基本不会触发。
  5. 当前服务端不会在收藏请求入口校验店铺 GUID 是否真实存在,而是依赖后续商店列表刷新时的 update_favorite_shop_guids() 清理失效收藏。
您需要登录后才可以回帖 登录 | registerss

本版积分规则

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

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

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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