☰
决战neo源码charinfo表解析:角色数据修改与避坑指南
2026/10/3 9:22:39 网站建设 项目流程

简介:Droiyan Online(决战Neo)服务端中角色信息与聊天通信模块的 C++ 源码包,面向有 C++/网络编程基础的游戏后端开发者和相关专业学习者,可用于剖析传统 MMO 服务器的通信机制与角色数据管理方式。资源共 75 个文件,包含 35 个头文件、19 个实现文件,另有少量 lib 库、工程配置、可执行文件与调试辅助文件;头文件负责接口声明,cpp 文件承载核心逻辑,lib 与工程文件便于编译复现,整包约 654KB,结构清晰紧凑,适合源码级研读。目前已有 1123 人学习浏览,具备不错的参考热度。通读这套源码可系统掌握聊天消息收发、用户登录与状态维护、Socket/IOCP 通信、数据压缩与多缓冲区管理、物品表加载等关键模块,同时也能看到老牌 MMO 的服务端工程组织方式,为二次开发或自制游戏服务器提供可落地的实现思路。

1. 决战 neo 源码里的 charinfo:老服务端里最容易被改坏的一张表

老牌韩游《Droiyan Online》(中文名《决战》)的服务端源码,在怀旧圈子里常年以带下划线的目录名流传,charinfo_droiyanOnline_决战neo源码就是其中一种打包风格。拆开看:charinfo 是数据库里存放角色信息的那张核心表,droiyanOnline 指向游戏服务端本身,决战 neo 则是这套服务端源码的版本标签。很多人拿到源码后卡在同一个地方——服务端能起来,但角色的等级、坐标、背包对不上号,一改就坏档。这篇文章把我实际折腾这套端的过程拆开讲:charinfo 谁在读、字段怎么对应游戏内人物、搭建和修改时哪些坑值得提前避开。适合手里已经有一份源码但跑不起来,或者想改角色属性又怕改坏存档的人。

2. 决战 neo 源码结构:先定位 charinfo 在哪个进程、哪个环节被读

拿到任何一份服务端源码,我做的第一件事不是急着编译,而是先把目录结构和进程模型摸清楚。决战老模拟器的代码布局和后来的商业引擎差别很大,charinfo 这张表有可能被三个以上进程分别读写,理不清这一点,后面改字段就是在黑匣子里拆弹。

2.1 一套典型源码包的目录与进程划分

决战 neo 系的服务端普遍沿用了早年商业端的进程拆分方式,常见的可执行模块有账号登录服务、世界/地图服务、AI 服务、数据库服务,外加一个面向运营的 GM 工具。源码包里通常能看到对应目录:LoginServer、WorldServer、MapServer、AIServer,以及放在 Tools 或 GM 目录下的后台管理程序。

这些进程之间的关系是单向依赖的:客户端先连登录服务,登录服务校验账号后把用户引导到世界服务;世界服务再加载地图和 AI,跑起来之后才真正读取 charinfo 里的角色数据。我一般会用下面这个最小进程表来判断一套刚下到的源码跑起来需要几台虚拟机:

模块职责是否直连数据库
LoginServer账号校验、服务器列表是
WorldServer角色列表、选角、世界广播是
MapServer地图场景、怪物AI、掉落部分版本独立
AIServer非玩家角色逻辑、定时事件否
GM Tool查角色、改属性、发物品是

如果你拿到的版本把 WorldServer 和 MapServer 合并成了一个程序,那登录服务起来之后只需要拉一个世界进程。从运维角度说,进程越少越好,因为老代码的内存管理普遍粗糙,多一个进程就多一个随机崩溃源。

2.2 数据读写点:登录选角时的 charinfo 查询

charinfo 在被客户端看到之前,至少要经历两轮数据库查询。第一轮发生在账号通过校验之后,登录服务会去查这个账号名下的角色列表;第二轮发生在玩家选角进图时,世界服务会读取角色的完整字段,包括坐标、属性、装备栏和背包。

我在老端源码里最常见的角色列表 SQL 长这样:

SELECT CharID, Name, Class, Level, Map, X, Y FROM charinfo WHERE AccountID = ? AND DeleteFlag = 0 ORDER BY CreateTime;

这条语句决定了选角界面能看到哪几个角色、每个角色显示什么信息。注意DeleteFlag = 0这个条件,它是软删除标记。如果把角色直接 DELETE 掉,与其关联的背包、任务数据全都会悬空;老端默认的做法是置 1 隐藏,这样后续查错还能把数据捞回来。

第二轮的完整读取比较重,涉及字段也多:

SELECT CharID, Name, Class, Level, Exp, Money, Str, Dex, Hp, Mp, Map, X, Y, Direction, Inventory, QuestLog FROM charinfo WHERE CharID = ?;

这里能看到 Inventory 和 QuestLog 这种大字段。很多新手以为它们存的是可读文本,实际是打包好的二进制串,字符串长度和内容由服务端内存结构决定。直接改这两个字段的值,极大概率导致客户端解析失败,表现为进图掉线。

2.3 为什么老模拟器偏爱 Delphi + ODBC:选型与代价

决战 neo 这类源码的语言分布很有规律,绝大多数是基于 Delphi 6/7 的工程,少部分重制版用 C++。不是当年的作者不想用新语言,而是商业端时代遗留下来的游戏服务和客户端封包结构全是 Delphi 的 Object Pascal 风格,换语言意味着把整个封包解析、内存布局全部翻译一遍,工程量等于重写服务器。

数据库访问层则以 ODBC 为主。ODBC 的好处是数据库厂商无关,一套代码可以对接 SQL Server 也能对接 MySQL;坏处是驱动版本敏感,后面搭建时很容易栽在MySQL ODBC 驱动位数不对这种低级问题上。相比之下,后来的 C# 重制版会直接用 ADO.NET,字符集和事务处理都省心很多,但这类版本的数量远不如 Delphi 原版多。

所以我的选型建议很直接:如果你只是自己在虚拟机里研究,优先跑 Delphi 原版,不要一上来就尝试把数据库迁移到 SQL Server。老代码里日期格式、字符串长度、自增主键的实现都和 MySQL 绑得很深,换库表面看是省了安装成本,实际会带来一堆字段类型不兼容的隐性问题。

3. 把决战 neo 服务端跑起来:数据库初始化、配置与启动验证

源码能编译通过只是第一步,真正决定能不能进游戏的是数据库初始化和启动顺序。我见过太多人卡在“编译好了但登录器显示服务器维护”,原因基本都在数据库脚本没导全,或者服务端进程起来的顺序错了。这一章按我验证过的最小步骤写下来。

3.1 环境与数据库:先把 MySQL 5.7 装对

决战 neo 老端对 MySQL 8.x 的兼容性很差,主要是密码认证插件从mysql_native_password换成了caching_sha2_password,ODBC 连接会在握手阶段直接报错。我一般直接装 MySQL 5.7,装完后再创建一个独立的库和账号,不要用 root 跑服务端。

# 安装 MySQL 5.7 之后,进入命令行创建专用库 mysql -uroot -p CREATE DATABASE droiyan DEFAULT CHARACTER SET gbk COLLATE gbk_chinese_ci; CREATE USER 'droiyan'@'localhost' IDENTIFIED BY 'droiyan_pass'; GRANT ALL PRIVILEGES ON droiyan.* TO 'droiyan'@'localhost'; FLUSH PRIVILEGES;

这里把字符集直接定成gbk,是我踩过坑之后固定下来的选择。老端客户端和脚本大多用 GB2312/GBK 编码,数据库用 utf8mb4 会导致中文角色名在写入时被转码,游戏内显示乱码,服务端日志也会跟着出现大量问号。如果你手里的源码版本明确是后来汉化重制的,再考虑 utf8mb4——判断方法很简单,打开一个 SQL 脚本看中文字符能不能正常显示,能正常显示就按脚本原来的字符集来建库。

3.2 导入数据库脚本:顺序比内容更容易错

源码包里一般有 sql 或 database 目录,里面是按编号排列的脚本。导入时最忌一次性source整个目录,因为表之间存在外键关联,角色表可能引用账号表的主键,而账号表可能引用权限表的初始数据。顺序错了,报错信息会很直白但也很误导人,比如提示Table 'charinfo' doesn't exist,实际原因是上一张表没建成功。

我一般按下面的顺序导入:

# 先建基础库结构和账号相关表 mysql -udroiyan -pdroiyan_pass droiyan < 1_create_account.sql # 再建角色表和关联表 mysql -udroiyan -pdroiyan_pass droiyan < 2_create_charinfo.sql # 最后灌入初始化数据:地图、物品模板、NPC mysql -udroiyan -pdroiyan_pass droiyan < 3_init_world_data.sql

每执行完一条,我会顺手查一下表数量,确认上一步真实生效后再导下一步:

SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'droiyan';

这个习惯能省掉很多“后面报错回头查前面”的时间。脚本导入完成后,重点检查三张表:account、charinfo、item_template。前两张是登录和选角的基础,第三张决定你后面用 GM 工具发装备时能不能查到物品 ID。

3.3 配置文件与启动顺序:先登录后世界

服务端程序本身并不配置数据库连接字符串,而是读一个 ini。老端常见的是server.ini或config.ini,里面至少包含数据库地址、端口、进程间监听的端口,以及当前服务器的名称。我遇到过的坑是多个程序共用同一个 ini,但每个进程实际读取的键名不完全一致,WorldServer 认[Database]下的DBName,LoginServer 却认[DBServer]下的ServerName。

我习惯在启动之前先用文本编辑器把关键字段核对一遍:

[Database] Host=127.0.0.1 Port=3306 DBName=droiyan User=droiyan Password=droiyan_pass [Network] LoginPort=7100 WorldPort=7200

这里的逻辑很简单:端口唯一,地址统一回环。如果你打算让局域网其他人一起连,Host 改成局域网 IP,并且数据库账号要允许非 localhost 访问。改完配置后,启动顺序不能乱,必须先起登录服务,再起世界服务。因为世界服务启动时会向登录服务注册自身的存在,如果反着来,登录服务的服务器列表里永远看不到这个区。

cd D:\droiyan_server start login_server.exe --config server.ini # 等 2-3 秒,确认 7100 端口已经在监听 netstat -ano | findstr 7100 start world_server.exe --config server.ini netstat -ano | findstr 7200

netstat的验证步骤不是可选操作。老程序没有清晰的启动日志,端口监听成功基本等于初始化完成。如果findstr查不到端口,去程序的同目录下找log文件夹,里面按日期生成的 txt 才是真正的报错来源。

4. charinfo 字段解析:把角色属性、坐标和背包对上号

搭建跑通之后,大多数人会想做第一件快事:把自己的角色改成满级。但直接打开 charinfo 表看到那一片字段,很快就不知道该动哪个。这一章把字段讲透,并给出改属性前必须做的三个检查。

4.1 常见字段表与字段命名差异

不同来源的决战 neo 源码,charinfo 表的字段名差异不小。有的版本叫Map,有的叫WorldNo;有的把属性拆成Str/Dex/Con/Int/Wis,有的干脆合并成一个StatPoint让玩家自己分配。我在不同版本里比较过,下面这组字段是最常见、也最稳定的交集:

字段名类型含义注意点
CharIDint角色唯一ID全服唯一,不要手动改
AccountIDint所属账号ID与 account 表关联
Namevarchar角色名长度上限由客户端决定
Classtinyint职业不同数值对应职业表
Levelint当前等级部分版本上限是 150
Expbigint当前经验值传值太大会变负数
Moneyint金币不能超出 int 上限
Hp / Mpint当前生命/魔法改完后需重登同步
Mapsmallint地图编号0 号图可能是新手村
X / Yint坐标注意是否放大 10 倍
Directiontinyint朝向0-7 对应八个方向
Inventoryblob/text背包数据非直接可读,慎改
QuestLogblob/text任务进度同上
DeleteFlagtinyint软删除标记0 正常,1 隐藏

看到Inventory和QuestLog字段时,先记住一个原则:这两个字段属于服务端内存结构的序列化结果,不是给人看的。想改背包,去找 GM 工具或者物品模板表,而不是直接 UPDATE 这两个字段。

4.2 坐标字段的十倍缩放问题

坐标字段是翻车率最高的地方。决战的客户端和服务端在坐标系统上存在一个换算关系:游戏内玩家看到的坐标是逻辑坐标,而数据库里存的是放大后的坐标。常见比例是 10 倍,也就是游戏内(123, 456)在数据库里是X=1230, Y=4560。

如果你忽略这个缩放,直接按游戏内看到的坐标去 UPDATE,角色重登后会发现自己在墙里、卡在地图边界,或者飞到了地图外的虚空区域。更隐蔽的问题是某些地图的空气墙检测直接读取数据库坐标,坐标落在墙内时服务端会判定角色卡死,然后触发定时传送逻辑,把角色随机甩回某个复活点。

判断你的版本是否走 10 倍缩放,最简单的方法:找一张已知出生点的地图,在数据库里对比游戏内坐标和 charinfo 里的值。差值正好是 10 的倍数,那就是缩放了。改坐标前看一眼旧值,基本能避开这个坑。

4.3 用 UPDATE 改等级和属性前的三个检查

直接改等级和属性点,比改背包安全得多,但也得先做三个检查。第一个检查是确认角色处于离线状态——服务端进程对内存中的角色有缓存,如果角色在线时改表,存档逻辑会把内存里的旧数据覆盖回数据库,你改的东西白改。

第二个检查是把旧值先 SELECT 出来,留个备份。习惯差的人改完才发现数值不对,想还原已经来不及,只能凭记忆敲回去。老端的自动存档频率通常在 30 秒到 5 分钟之间,你改完表再等两分钟,存档一跑,错误数据就被覆盖固化,想回滚都没得回。

第三个检查是确认没有触发服务端的数值校验。部分版本在角色登录时会检查 Level 对应的 Exp 是否合法,等级改成 99 但经验还是 1 级的,可能被判定为非法数据,直接把角色踢下线甚至清空。

-- 改之前,先把原始值查出来 SELECT CharID, Name, Level, Exp, Str, Dex, Con FROM charinfo WHERE Name = 'test_char'; -- 确认离线后,执行修改 UPDATE charinfo SET Level = 99, Exp = 999999999, Str = 500, Dex = 500, Con = 500 WHERE Name = 'test_char';

改完再重登,角色属性才会被服务端重新加载。如果重登后属性变回去了,说明这个版本的服务端用了频繁存档逻辑,你需要在服务端停止的状态下改,或者改完立刻把进程杀掉再重启。杀掉之前要确认没有其他玩家在线,老端没有热加载能力,停服改表是唯一可靠路径。

5. 排查与避坑:改 charinfo 时最容易翻车的五个现场

这一章写的都是我在实战里遇到过、或者帮别人排查过的问题。每条按「现象 → 原因 → 解决」的顺序说清楚,前面几章的坑在这里会集中体现。

5.1 新角色卡在出生点:World 与 X/Y 初始值没有对齐

现象:角色建号成功,进游戏后一直站在原地不能动,小地图显示在错误位置,甚至直接掉到地图边缘的虚空中。删除角色重建,问题依旧。

原因:建号 SQL 在插入 charinfo 时,Map 写成了 0,X/Y 写成了 0,但 0 号地图的 0,0 坐标并不在合法寻路网格上。老端在创建角色时会调用一段初始化逻辑写入出生点,但你拿到的源码版本里这段逻辑可能依赖另一张地图配置表,而那张表的数据没有正确导入。

解决:先查地图表里新手村的地图编号和出生坐标,然后直接 UPDATE 这个新角色。注意确认坐标是否 10 倍缩放。修复后重登,不要通过传送到其他地图来尝试避开,那样只会让问题继续潜伏。

SELECT MapID, BirthX, BirthY FROM world_map WHERE MapName LIKE '%新手%'; UPDATE charinfo SET Map = 1, X = 5000, Y = 5000 WHERE Name = 'test_char';

5.2 中文角色名一片乱码:字符集在导入那一步就错了

现象:游戏内创建中文角色名,进入游戏后名字显示为一串问号,或者好友列表里无法正确显示。数据库里看 charinfo 的 Name 字段,内容是正常的,但客户端就是乱码。

原因:服务端进程与数据库连接的字符集不匹配。建库用的是 GBK,但服务端 ODBC 连接串里没有指定charset=gbk,MySQL 5.7 默认可能是 latin1 连接,字符在传输过程中被错误转码。

解决:在数据库连接串里显式加上字符集参数。老端大多通过 ODBC 系统 DSN 连接,DSN 配置里找到 Character Set 选项,改成 GBK 或 GB2312,重启服务端进程。如果还不行,检查客户端用的汉化补丁是否自带字符集要求,个别版本客户端固定使用 UTF-8 显示,这种情况需要把整个库的字符集换成 utf8mb4,并重新导一遍中文相关表。

5.3 直接改背包字段后掉线:Inventory 和槽位解析对不上

现象:用 Navicat 打开 charinfo 表,把 Inventory 字段里的二进制值改了一段,然后角色登录后背包空了一半,或者一打开背包就闪退。

原因:Inventory 字段是按服务端内存结构逐字节打包的,包含物品数量、槽位索引、耐久度、绑定标记。你改的是整个字符串的长度或某个字节,结构解析直接失败。

解决:不要碰 Inventory 和 QuestLog 字段。发装备、删物品、改数量,一律走 GM 工具。如果源码包里的 GM 工具不能发装备,查item_template表拿到物品 ID,再用服务端自带的调命令发送。举个例子,常见命令格式是/giveitem 角色名 物品ID 数量,具体命令名去 AIServer 的脚本目录里搜giveitem关键词,不同版本叫makeitem或additem。

5.4 地图服务器启动即闪退:DAT 资源版本与服务端不匹配

现象:WorldServer 或 MapServer 启动后一两秒内直接退出,屏幕上没有报错,进入程序目录看 log 文件,出现地图读取失败相关字样。

原因:决战的地图数据以 DAT 文件形式存在服务端资源目录里,这些 DAT 文件的版本必须与服务端源码编译时的客户端资源版本一致。如果源码包里混入了不同客户端版本的 DAT,服务端在解析地图网格时发生越界或空指针。

解决:先看日志定位是哪个地图文件失败,然后把资源目录下的 DAT 文件与客户端目录里的同名文件做哈希对比。最常见的做法是直接拷一份与原源码配套的完整资源目录覆盖过去。这里很多人会图省事,只把报错的那一个文件替换掉,结果下一个地图又崩。正确做法是校验整个地图资源目录的版本一致性。

检查项具体操作
日志定位查看 log 下最近生成的 txt,找load map fail或dat error
资源比对用哈希工具对比服务端和客户端的 DAT 文件
整体替换用完整配套资源目录替换,不要单文件覆盖

5.5 GM 工具连不上数据库:ODBC 驱动和系统 DSN 问题

现象:GM 工具打开后提示无法连接数据库,但同一个机器上服务端能正常跑,说明数据库本身没毛病。

原因:老 GM 工具走 ODBC 连接,安装的是 64 位 MySQL ODBC 驱动,但 GM 工具本身是 32 位程序,Windows 的 ODBC 管理器分 32 位和 64 位两套,驱动注册表不互通。

解决:卸载现有 ODBC 驱动,安装对应位数的 MySQL ODBC 5.3 驱动,然后在 32 位 ODBC 管理器里新建一个系统 DSN。32 位 ODBC 管理器的路径是C:\Windows\SysWOW64\odbcad32.exe,这一点经常被忽略,因为界面看起来和 64 位版一模一样。

6. 用查询和触发器给 charinfo 上一道保险:进阶验证技巧

折腾到最后,我养成了一个习惯:所有对 charinfo 的改动都先留下痕迹。这里分享两个可以直接抄走的技巧,一个是角色数据体检 SQL,一个是自动备份触发器。

6.1 一条 SQL 体检全部角色数据

改完一批角色后,我用下面这条 SQL 做整体体检,能同时查出等级超上限、地图编号非法、坐标落在边界外的角色:

SELECT Name, Level, Map, X, Y FROM charinfo WHERE Level > 150 OR Map = 0 OR X > 60000 OR Y > 60000;

数值范围按自己端的实际地图尺寸调整。地图最大坐标可以直接查world_map表里的宽高字段。运行时发现异常角色,先复制原始行再处理,别直接 UPDATE。

6.2 建触发器给误改留后悔药

MySQL 触发器可以把旧数据在修改前备份到一张历史表。这个操作做一次,后面所有误操作都有后悔药:

CREATE TABLE charinfo_bak LIKE charinfo; ALTER TABLE charinfo_bak ADD COLUMN bak_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP; DELIMITER // CREATE TRIGGER tri_charinfo_before_update BEFORE UPDATE ON charinfo FOR EACH ROW BEGIN INSERT INTO charinfo_bak (CharID, AccountID, Name, Level, Exp, Map, X, Y) VALUES (OLD.CharID, OLD.AccountID, OLD.Name, OLD.Level, OLD.Exp, OLD.Map, OLD.X, OLD.Y); END// DELIMITER ;

有了这张备份表,我每次改数据前心里都有底。真改错了,一条 INSERT SELECT 就能把旧值翻回来。这套服务端源码的核心价值就在 charinfo 这张表上,搞懂它的读写关系,比追新框架更值得投入时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询