传奇NPC脚本非法字符过滤实战:@@InPutString输入拦截与CheckStrLen/FilterList全链路

来源: 作者: 点击:
@@InPutString 指令触发后客户端弹文本输入框,玩家录入内容写入 S 变量(S0–S99),后续脚本通过 <$STR(SX)> 读取。这条链路没有任何内置转义,玩家输入 |、/、\、<、>、$、@、脚本关键字、系统命令字符串,都会原样进入变量。如果服务端逻辑用该变量拼接数据库查询、写 TXT 记录、做 EQUAL/CHECKLEVELEX 之外的字符串比对,脏数据直接穿透。过滤必须在“回车确认后、业务逻辑前”这一段完成,不能依赖客户端弹窗自身的限制。

一、引擎级过滤列表(第一道闸)

Hero 引擎路径:M2Server → 查看 → 列表信息 → 其他设置 → 字符过滤,或编辑 Mir200\Envir\GuildRankNameFilter.txt(部分版本读此文件)。添加每行一个敏感词或非法字符序列,玩家录入命中后不进入回调段,直接跳转 [@IsInFilterList](3K 引擎跳 [@MsgFilter])。

配置示例(GuildRankNameFilter.txt):

@
@@
~
..
\
/
<
>
|
$
script
call
goto
exec


回调处理:
[@IsInFilterList]
#SAY
你输入的内容包含非法字符,请重新输入!\
<返回/@Main>


3K 引擎对应:
[@MsgFilter]
#SAY
输入内容命中过滤列表,请重新输入!\
<返回/@Main>


注意:过滤列表是全局生效的,不仅拦 @@InPutString,行会名称创建、角色改名、聊天频道也会走同一张表。往里加 @、$、<、>、| 这些脚本语法符号是最低限度的防护。

二、回调段内变量判空与长度校验(第二道闸)

Hero 引擎 @@InPutStringX 回调段名为 [@CallInPutStringX],3K 为 [@InPutStringX]。进入回调后第一件事是判空和长度,用 CheckStrLen 指令:
[@CallInPutString10]
#IF
CheckStrLen <$STR(S10)> 1
#ACT
MESSAGEBOX 输入不能为空!
GOTO @Main
#IF
CheckStrLen <$STR(S10)> 16
#ACT
MESSAGEBOX 输入长度不能超过16个字符!
GOTO @Main
#ELSE
#ACT
; 通过校验,进入业务逻辑
GOTO @ProcessInput


CheckStrLen 语法:CheckStrLen <字符串> 长度值,不带比较符时判定“大于等于该长度”;部分引擎支持 CheckStrLen <字符串> < 5 表示小于5。实际写法以引擎版本为准,保守做法是用两次 CheckStrLen 分别卡下限和上限。

三、字符级逐字扫描(第三道闸)

引擎过滤列表只能做子串匹配,不能做白名单。如果需要严格限制“只允许中文+字母+数字”,用脚本逐字符遍历 S 变量,配合 CheckContainsStr 或 EQUAL 比对 ASCII 范围。由于传奇脚本没有正则,只能用笨办法——把输入拆成单字符逐一比对:
[@ProcessInput]
; 假设 S10 已通过长度校验
; 用 MOV S11 = 首字符,循环判断是否在白名单内
; 以下为逻辑示意,实际引擎需借助自定义变量+跳转实现

#IF
CheckContainsStr <$STR(S10)> @
#ACT
MESSAGEBOX 包含非法字符@!
GOTO @Main
#IF
CheckContainsStr <$STR(S10)> <
#ACT
MESSAGEBOX 包含非法字符<!
GOTO @Main
#IF
CheckContainsStr <$STR(S10)> >
#ACT
MESSAGEBOX 包含非法字符>!
GOTO @Main
#IF
CheckContainsStr <$STR(S10)> |
#ACT
MESSAGEBOX 包含非法字符|!
GOTO @Main
#IF
CheckContainsStr <$STR(S10)> $
#ACT
MESSAGEBOX 包含非法字符$!
GOTO @Main
#IF
CheckContainsStr <$STR(S10)> \
#ACT
MESSAGEBOX 包含非法字符\!
GOTO @Main
#IF
CheckContainsStr <$STR(S10)> /
#ACT
MESSAGEBOX 包含非法字符/!
GOTO @Main
#ELSE
#ACT
GOTO @SaveToDB


CheckContainsStr 用法:CheckContainsStr <源字符串> 子串,命中返回真。逐条写虽然冗长,但覆盖完全,且每条都能给出精确错误提示。

四、数据库写入前的二次校验

如果输入内容最终要写 TXT 或入库(如角色改名、行会公告、自定义称号),在 WRITETEXT/WRITELINE 之前再加一道 EQUAL 比对,防止空串或纯空格:
[@SaveToDB]
#IF
EQUAL S10
#ACT
MESSAGEBOX 内容为空,拒绝写入!
GOTO @Main
#ELSE
#ACT
WRITETEXT ..\Envir\UserData\Titles.txt <$STR(S10)>
SENDMSG 6 称号[<$STR(S10)>]已记录!
CLOSE


五、不同引擎的回调差异速查

项目 Hero 引擎 3K 引擎

触发指令 @@InPutStringX @@InPutStringX

回调段名 [@CallInPutStringX] [@InPutStringX]

过滤命中段 [@IsInFilterList] [@MsgFilter]

变量范围 S0–S99(部分支持扩展) S0–S99

判空方式 CheckStrLen <$STR(SX)> 1 EQUAL SX 或 CheckStrLen

过滤列表位置 列表信息→其他设置 / GuildRankNameFilter.txt 插件消息过滤列表

六、完整三层过滤示例(Hero 引擎,角色自定义称号)
[@Main]
请为角色设定一个称号:<输入称号/@InputTitle>\

[@InputTitle]
@@InPutString20 请输入称号(1-10字,仅限中文/字母/数字):

[@CallInPutString20]
; 第一层:引擎过滤列表已自动拦截,命中走 [@IsInFilterList]
#IF
CheckStrLen <$STR(S20)> 1
#ACT
MESSAGEBOX 称号不能为空!
GOTO @Main
#IF
CheckStrLen <$STR(S20)> 11
#ACT
MESSAGEBOX 称号不能超过10个字符!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> @
#ACT
MESSAGEBOX 称号不能包含@符号!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> <
#ACT
MESSAGEBOX 称号不能包含<符号!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> >
#ACT
MESSAGEBOX 称号不能包含>符号!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> |
#ACT
MESSAGEBOX 称号不能包含|符号!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> $
#ACT
MESSAGEBOX 称号不能包含$符号!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> \
#ACT
MESSAGEBOX 称号不能包含\符号!
GOTO @Main
#IF
CheckContainsStr <$STR(S20)> /
#ACT
MESSAGEBOX 称号不能包含/符号!
GOTO @Main
#ELSE
#ACT
WRITETEXT ..\Envir\UserData\Title_<$USERNAME>.txt <$STR(S20)>
SENDMSG 6 称号[<$STR(S20)>]设定成功!
CLOSE

[@IsInFilterList]
#SAY
输入的称号包含系统保留字符,请更换内容!\
<重新输入/@Main>


七、常见故障排查

• 输入后脚本无响应:检查回调段名是否按引擎写对(Hero 漏 Call、3K 多 Call 都会死)

• 过滤列表加了词但不生效:确认 M2 是否执行了“重新加载列表信息”或重启;GuildRankNameFilter.txt 改完需重载

• CheckContainsStr 不识别中文:部分老版本引擎只支持单字节字符匹配,中文需用 CheckStrLen 间接限制长度+过滤列表兜底

• 变量读出来是空:确认 @@InPutStringX 的 X 与回调段里读的 SX 编号一致,别写成 @@InPutString20 却在 [@CallInPutString20] 里读 S10

• 过滤列表误杀正常输入:逐条排查,把常用标点(如空格、下划线)从过滤文件中移除,改用脚本层 CheckContainsStr 精确拦截

@@InPutString 的过滤不是单一指令能解决的,必须三层叠加:引擎过滤列表拦语法符号和敏感词 → 回调段 CheckStrLen 卡长度和判空 → CheckContainsStr 逐字符白名单/黑名单扫描 → 写入前 EQUAL 最终确认。少任何一层,输入链路都存在被脏数据击穿的可能。