一、引擎内置过滤机制与触发链路
@@InPutStringX 弹框输入后,客户端把字符串发回服务端,引擎先做第一层校验:命中 M2 过滤列表 → 直接跳转 [@IsInFilterList](Hero端)或 [@MsgFilter](3K端),不写入 S 变量、不执行后续脚本。这一层是硬编码在引擎里的,脚本层面无法绕过,但过滤列表的内容完全由服务端维护。
过滤列表位置:M2控制器 → 查看 → 列表信息 → 其他设置 → 过滤字符(部分版本存为 GuildRankNameFilter.txt 或 FilterChar.txt)。每行一个词条,支持纯文本匹配。玩家输入包含任意词条即触发跳转。
二、脚本层二次校验:长度、字符集、格式
内置过滤只管"有没有命中词条",不管长度超限、纯数字、特殊符号组合等问题。实际脚本必须在回调段里加 #if 条件判断:
[@CallInPutString10]
if
EQUAL S10
act
MESSAGEBOX 输入不能为空
GOTO @main
elsesay
if
LARGE $STRLEN(S10) 20
act
MESSAGEBOX 输入长度不能超过20个字符
GOTO @main
elsesay
; 继续正常逻辑
act
SENDMSG 6 输入内容:S10
说明:部分引擎支持 $STRLEN() 函数取字符串长度,不支持的用自定义计数或限制输入源(客户端输入框本身有字节上限)。
三、逐字符扫描检测特殊符号
对于需要严格限制字符集的场景(如行会名只允许中文+字母+数字),用循环或多次 CHECKCONTAINS 判断:
if
CHECKCONTAINS S10 ~
act
MESSAGEBOX 不能包含~符号
GOTO @main
elsesay
if
CHECKCONTAINS S10 `
act
MESSAGEBOX 不能包含`符号
GOTO @main
elsesay
if
CHECKCONTAINS S10 |
act
MESSAGEBOX 不能包含|符号
GOTO @main
elsesay
if
CHECKCONTAINS S10 <
act
MESSAGEBOX 不能包含<符号
GOTO @main
elsesay
if
CHECKCONTAINS S10 >
act
MESSAGEBOX 不能包含>符号
GOTO @main
elsesay
if
CHECKCONTAINS S10 &
act
MESSAGEBOX 不能包含&符号
GOTO @main
elsesay
; 全部通过才执行
act
GOTO @process
需要屏蔽的字符清单(按危害程度排序):
• |:部分版本脚本解析器把 | 当命令分隔符,注入后可执行额外指令
• < >:脚本标签语法符号,输入含 < 可能被误解析为标签跳转
• ~:变量展开符号,某些引擎里 ~S10 会展开为全局变量
• &:条件连接符,可能干扰 #if 解析
• ' " :SQL注入场景(如果后端用文本数据库且未转义)
• \r \n:换行注入,破坏脚本块结构
四、数字型输入的双重保险
即使弹的是 @@InPutString,玩家也可能输入纯数字。如果需要数字,回调段里用 EQUAL/NOTEQUAL + 范围判断:
[@CallInPutString11]
if
EQUAL S11 0
act
MESSAGEBOX 不能为0
GOTO @main
elsesay
if
SMALL N11 1
act
MESSAGEBOX 不能小于1
GOTO @main
elsesay
if
LARGE N11 9999
act
MESSAGEBOX 不能超过9999
GOTO @main
elsesay
act
; S11 已确认为合法数字字符串,N11 为对应数值
SENDMSG 6 确认数值:N11
注意:@@InPutString 的结果在 S 变量里,但引擎同时会尝试解析为数字存入同名 N 变量(如 S11 → N11)。如果输入含非数字字符,N11 为 0,可以用这个特性做"纯数字校验"——N11==0 且 S11 不为 "0" 说明含字母。
五、数据库落盘前的转义处理
输入最终要写入文本文件或数据库时,必须做转义:
1. 写文本文件(SAVEVAR):
变量内容含换行符会破坏文件格式。在写入前用 REPLACE 函数清除或替换危险字符:
CALCVAR GLOBAL Z1 = $REPLACE(S10, "|", "")
CALCVAR GLOBAL Z1 = $REPLACE(Z1, "<", "")
CALCVAR GLOBAL Z1 = $REPLACE(Z1, ">", "")
SAVEVAR GLOBAL Z1 ..\QuestDiary\数据文件\InputLog.txt
2. 写 DBC2000 / Access:
通过脚本命令写入数据库的记录,字段长度限制要在脚本层提前拦截。比如 HeroDB 的 Guild 表 Name 字段 varchar(20),脚本里用 $STRLEN 限制在20字符内,超长截断或拒绝。
六、行会名/角色名注册完整过滤示例
[@CallInPutString5]
; 第一层:空值
if
EQUAL S5
act
MESSAGEBOX 行会名不能为空
GOTO @main
elsesay
; 第二层:长度
if
LARGE $STRLEN(S5) 16
act
MESSAGEBOX 行会名最多16个字符
GOTO @main
elsesay
; 第三层:非法字符
if
CHECKCONTAINS S5 ~
act
MESSAGEBOX 不能包含~符号
GOTO @main
elsesay
if
CHECKCONTAINS S5 <
act
MESSAGEBOX 不能包含<符号
GOTO @main
elsesay
if
CHECKCONTAINS S5 >
act
MESSAGEBOX 不能包含>符号
GOTO @main
elsesay
if
CHECKCONTAINS S5 |
act
MESSAGEBOX 不能包含|符号
GOTO @main
elsesay
; 第四层:重复检查
if
CHECKTEXTLIST ..\QuestDiary\Guild\GuildNames.txt S5
act
MESSAGEBOX 该行会名已被注册
GOTO @main
elsesay
; 第五层:引擎内置过滤(自动跳@IsInFilterList,此处不用写)
; 全部通过
act
ADDTEXTLIST ..\QuestDiary\Guild\GuildNames.txt S5
SENDMSG 6 行会名[ S5 ]注册成功
[@IsInFilterList]
act
MESSAGEBOX 行会名含非法词汇,请重新输入
GOTO @main
七、M2过滤列表维护规范
过滤列表不是越长越好,维护原则:
• 按功能分区:行会名过滤、聊天过滤、任务输入过滤分开建表(不同版本支持多个过滤列表文件)
• 定期清理:过时的敏感词删掉,避免误拦正常输入
• 测试流程:每次加新词条后,用测试号走一遍完整输入流程,确认不误伤
• 备份:过滤列表文件改前复制一份 .bak,改崩了能回滚
八、多引擎差异汇总
项目 Hero引擎 3K引擎 GOM/GEE
回调段名 [@CallInPutStringX] [@InPutStringX] [@InPutStringX]
过滤跳转段 [@IsInFilterList] [@MsgFilter] [@MsgFilter]
过滤列表位置 M2→列表信息→其他设置 同左 同左
变量存储 SX + NX SX + NX SX + NX
CHECKCONTAINS 支持 支持 支持
$STRLEN 部分版本支持 部分版本支持 商业版支持
最大输入长度 客户端限制约60字节 同左 同左
九、调试与日志
输入异常时排查步骤:
1. 确认是否触发了 [@IsInFilterList] → 说明命中引擎过滤列表,去 M2 列表里查对应词条
2. 没触发过滤但后续逻辑异常 → 在回调段第一行加 SENDMSG 6 调试:S10,看实际写入内容
3. 变量读出来是空的 → 检查回调段名是否拼对(Hero 必须带 Call)
4. 中文变乱码 → 脚本文件存成 UTF-8 了,改 ANSI
5. 特殊符号没被拦住 → CHECKCONTAINS 的字符可能被引擎吃掉了,换成十六进制比较或升级引擎版本
十、完整防御纵深总结
玩家按键 → 客户端输入框(长度限制) → @@InPutStringX 发包 → 引擎层过滤列表匹配(@IsInFilterList 拦截) → 写入 S 变量 → 回调段脚本校验(空值/长度/字符集/格式/重复) → 转义处理 → 落盘(文本/DB)。五层防线,任意一层漏了还有下一层兜底。脚本层校验永远不能省——引擎过滤列表只管"有没有这个词",不管业务逻辑合不合法。
传奇NPC脚本中@@InPutString非法字符过滤实战:从输入拦截到数据落盘
来源:
作者:
点击:

