找回密码
 register
搜索
查看: 19|回复: 0

[文档笔记] 鉴定卷轴导致服务端崩溃的修复方案

[复制链接]
  • 打卡等级:本地老炮
  • 打卡总天数:584
  • 打卡月天数:20
  • 打卡总奖励:581
  • 最近打卡:2026-08-29 14:32:15
Waylee 发表于 2026-8-19 21:47 | 显示全部楼层 |阅读模式 | Google Chrome | Windows 10

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

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

×

一、问题现象

玩家使用鉴定卷轴时,客户端提交了非法的目标背包位置,服务端没有验证目标物品是否存在,直接进入鉴定逻辑。

原始代码:

return SendUseItemResult(pGamePlayer, pHuman, bagIndex,
    (USEITEM_RESULT)pHuman->UseIdentScroll(
        pPacket->GetBagIndex(),
        pPacket->GetTargetItem()));

UseIdentScroll() 内部继续读取目标物品,最终可能对空指针执行物品操作,导致:

SEG exception
Server crash

二、修复位置

修改文件:

Server/Server/GameServer/Server/Packets/CGUseItemHandler.cpp
Server/Server/GameServer/Server/Obj/Obj_Human.cpp

三、封包处理入口增加检查

CGUseItemHandler::Execute() 的鉴定卷轴分支中,先验证目标背包位置和目标物品。

if(IsIdentScroll(pBagItem))
{
    const BYTE targetItemIndex = pPacket->GetTargetItem();

    // 鉴定目标来自客户端,必须先检查背包位置。
    if(targetItemIndex >= MAX_BAG_SIZE)
    {
        CacheLog(LOG_FILE_1,
            "ERROR CGUseItemHandler Invalid ident target "
            "GUID=%X BagIndex=%d TargetItem=%d",
            pHuman->GetGUID(),
            bagIndex,
            targetItemIndex);

        return SendUseItemResult(
            pGamePlayer,
            pHuman,
            bagIndex,
            USEITEM_IDENT_TARGET_TYPE_FAIL);
    }

    Item* pTargetItem =
        HumanItemLogic::GetItem(pHuman, targetItemIndex);

    // 目标位置为空时,直接拒绝本次鉴定。
    if(pTargetItem == NULL || pTargetItem->IsEmpty())
    {
        return SendUseItemResult(
            pGamePlayer,
            pHuman,
            bagIndex,
            USEITEM_IDENT_TARGET_TYPE_FAIL);
    }

    return SendUseItemResult(
        pGamePlayer,
        pHuman,
        bagIndex,
        (USEITEM_RESULT)pHuman->UseIdentScroll(
            pPacket->GetBagIndex(),
            targetItemIndex));
}

这里必须使用服务端重新获取的 pTargetItem,不能只相信客户端传入的背包索引。

四、鉴定函数内部增加二次保护

修改:

Obj_Human.cpp

Obj_Human::UseIdentScroll() 开始位置增加检查:

INT Obj_Human::UseIdentScroll(
    UINT BagIndex,
    UINT TargetBagIndex)
{
    __ENTER_FUNCTION

    // 鉴定位置来自客户端,先限制背包范围。
    if(BagIndex >= MAX_BAG_SIZE)
    {
        return USEITEM_IDENT_TYPE_FAIL;
    }

    if(TargetBagIndex >= MAX_BAG_SIZE)
    {
        return USEITEM_IDENT_TARGET_TYPE_FAIL;
    }

    Item* pScrollItem =
        HumanItemLogic::GetItem(this, BagIndex);

    Item* pTargetItem =
        HumanItemLogic::GetItem(this, TargetBagIndex);

    Assert(pScrollItem != NULL && pTargetItem != NULL);

    // 源物品或目标物品不存在时,拒绝操作。
    if(pScrollItem == NULL || pScrollItem->IsEmpty())
    {
        return USEITEM_IDENT_TYPE_FAIL;
    }

    if(pTargetItem == NULL || pTargetItem->IsEmpty())
    {
        return USEITEM_IDENT_TARGET_TYPE_FAIL;
    }

    if(pScrollItem->GetItemClass() != ICLASS_COMITEM ||
       pScrollItem->GetItemQual() != COMITEM_QUAL_MIS)
    {
        return USEITEM_IDENT_TYPE_FAIL;
    }

    // 后续保留原有鉴定逻辑。

入口和业务函数都要检查,是为了避免其他服务器内部调用绕过封包入口后再次访问无效物品。

五、修复原则

鉴定流程必须按照以下顺序执行:

检查源背包索引
    ↓
检查目标背包索引
    ↓
读取源物品
    ↓
读取目标物品
    ↓
检查物品是否为空
    ↓
检查源物品是否为鉴定卷轴
    ↓
执行原有鉴定逻辑

不能直接使用客户端传入的目标位置访问物品,也不能只依赖:

Assert(pTargetItem);

Assert 只适合调试阶段,正式运行时仍然必须使用明确的空指针判断和失败返回。

日志中可以看到错误记录,但不会再出现空指针访问和服务端崩溃。

您需要登录后才可以回帖 登录 | register

本版积分规则

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

GMT+8, 2026-8-30 10:58 , Processed in 0.064048 second(s), 26 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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