20/04/26 23:49:34.70 [:000000a8] lua call [382 to :a8 : 0 msgsz = 203] error : [31m./framework/lualib/skynet.lua:988: ./framework/lualib/skynet.lua:452: ./lualib/iostream.lua:163: bad argument #2 to 'pack' (unsigned overflow)
stack traceback:
[C]: in function 'string.pack'
./lualib/iostream.lua:163: in method 'writeuint'
./services/game/packet.lua:10638: in method 'bos'
./lualib/net.lua:196: in function 'net.send2client'
./services/msgagent.lua:457: in local 'f'
./services/msgagent.lua:3284: in upvalue 'f'
./framework/lualib/skynet.lua:402: in function <./framework/lualib/skynet.lua:374>
stack traceback:
[C]: in function 'assert'
./framework/lualib/skynet.lua:988: in function 'skynet.dispatch_message'[0m
帮会城市递交过期银票报 unsigned overflow 的最终修法
最近在补帮会城市老脚本时,又遇到一个很有代表性的兼容问题。
玩家在钱庄 NPC 处点击“递交过期银票”后,服务端直接抛出一段很吓人的错误日志:
bad argument #2 to 'pack' (unsigned overflow)
表面上看,很多人第一反应会怀疑:
- 是不是银票金额太大了
- 是不是帮会资金溢出了
- 是不是广播消息字符串有问题
但这次真正的根因,其实和金额、广播都没关系,而是一个非常典型的“老脚本负数哨兵值”和 Lua uint32 打包规则不兼容的问题。
这篇就不写中间排查过程了,只写最终稳定方案,方便后面直接照着改。
一、先说最终结论
这次最终只需要修改一个文件:
\home\ubuntu\Game2\lualib\script_base.lua
不需要改客户端。
也不建议只在 city0_building5.lua 单点硬修。
最终修法的核心思路只有一句话:
老脚本里传入的 -1,在需要写入 uint32 的脚本事件封包里,先转换成 0xFFFFFFFF 再发给客户端。
这样做以后:
- “递交过期银票”恢复正常
- 不会再触发
string.pack("I4", -1) 的 unsigned overflow
- 以后其他仍然沿用老脚本写法的同类场景,也能一起受益
二、问题到底出在哪
当前帮会城市钱庄脚本里,“递交过期银票”的入口在:
\home\ubuntu\Game2\services\scripts\city0_building5.lua
关键代码是这段:
elseif index == 7 then
self:BeginEvent(self.script_id)
self:AddText("请把过期银票拖入到第一个物品格中!")
self:EndEvent()
self:DispatchMissionDemandInfo(selfId, targetId, self.script_id, -1, 2)
注意第四个参数:
-1
这在老脚本时代其实是很常见的写法,它表示一个“无任务 ID”或“占位任务 ID”的哨兵值。
问题在于,Lua 版服务端这里最终走的是:
script_base:DispatchMissionDemandInfo
packet.GCScriptCommand
m_nCmdID == 3
而这个分支在发包时,会把 m_idMission 当成 uint32 去写:
stream:writeuint(self.m_idMission)
Lua 的 string.pack("I4", n) 不能直接接受 -1。
所以一旦这里把 -1 原样塞进去,就会立刻触发:
bad argument #2 to 'pack' (unsigned overflow)
三、为什么我不建议直接改 city0_building5.lua
有人会说,那不是很简单吗?
把这里的:
-1
直接改成:
0
或者别的正数不就好了。
这种改法短期能躲过这一个点,但我不建议这样做。
原因很简单:
- 老脚本里用
-1 作为哨兵值是历史惯例
- 今天是
city0_building5.lua,明天别的脚本也可能继续这样传
- 如果只改单个脚本,后面同类问题还会在别处重复出现
更稳的做法,是在兼容层把这类老写法统一吃掉。
也就是说:
- 老脚本仍然可以继续传
-1
- Lua 服务端在真正发
uint32 封包前,自动把负数转换成无符号值
这才是更适合 skynet Lua 迁移环境的修法。
四、最终修改写法
本次最终修改的位置在:
\home\ubuntu\Game2\lualib\script_base.lua
主要改了两个函数:
DispatchMissionInfo
DispatchMissionDemandInfo
1. DispatchMissionInfo 的最终写法
function script_base:DispatchMissionInfo(selfId, targetId, script_id, mission_id)
local m_nCmdID = 1
local ret = packet_def.GCScriptCommand.new()
ret.m_nCmdID = m_nCmdID
ret.event = self.event
local target_value = tonumber(targetId or define.INVAILD_ID) or define.INVAILD_ID
if target_value < 0 then
target_value = target_value % 0x100000000
end
ret.target_id = target_value
ret.size = #ret.event
ret.m_objID = script_id
ret.m_idMission = mission_id
ret.m_yBonusCount = #self.m_aBonus
self.scene:send2client(selfId, ret)
end
这里先把 target_id 做了数值归一化。
如果目标 ID 是负数,比如老接口里的 -1,就统一转成:
target_value % 0x100000000
也就是把它转换成 uint32 语义下的值。
2. DispatchMissionDemandInfo 的最终写法
***付费内容***
这才是这次修复的真正核心。
这里我做了三层兼容:
targetId 先转成数字
mission_id 先转成数字
done 先转成数字
然后统一做一条规则:
如果值小于 0,就转成 uint32 语义下的等价值
也就是:
value = value % 0x100000000
举个最典型的例子:
-1 % 0x100000000
结果就是:
4294967295
这正好就是 0xFFFFFFFF,也正是老 C++ 时代里很多无效 ID 在无符号 32 位下的表达方式。
所以这样改完后:
- 老脚本仍然保持原样
- 发包层拿到的是合法的
uint32
string.pack("I4", n) 不会再报溢出
五、为什么这套修法更稳
我这次没有去动 city0_building5.lua 的那句 -1,原因很明确。
因为问题不是“这个脚本特例写错了”,而是:
当前 Lua 兼容层没有把老脚本里常见的负数哨兵值处理好。
如果只改单点脚本,会有几个明显问题:
- 以后别的老脚本还会继续踩同样的坑
- 同类问题会在
DispatchMissionInfo、DispatchMissionDemandInfo 这类封包分发函数外不断重复
- 逻辑会越来越碎,后面不好维护
而把修复放到 script_base.lua 的好处是:
- 一次修复,所有同类脚本共同受益
- 老脚本可以继续按历史写法保留
- 兼容层职责更清晰
这就是为什么我把它定义成“兼容层修复”,而不是“单脚本热补”。
剩余 12% 内容需要支付 50.00
金币 后可完整阅读
支持付费阅读,激励作者创作更好的作品。