资讯动态

MirServer-Delphi服务端源码深度解析与MySQL适配指南

发布时间:2026/9/14 12:57:56 来源:尧图企业网站定制
简介本资源为《传奇2》Mir2服务器端完整Delphi实现源码面向游戏服务端开发爱好者、逆向学习者及Delphi资深开发者适用于复现经典MMORPG通信逻辑、研究封包结构与服务端架构设计。资源共944个文件以360个Pascal单元.pas、105个窗体定义.dfm和30个工程主文件.dpr为核心辅以配置文件.cfg/.ini、HTML文档、资源文件.res/.bmp及可执行工具.exe/.bat完整覆盖登录、地图、物品、NPC等核心模块压缩包大小52.59MB结构清晰便于按功能模块分层研读。已有2334人学习下载读者可直接获取含完整编译环境的Delphi服务端工程深入理解物品名称长度字段name len等关键协议细节验证封包格式修正思路并基于现有结构快速调试、扩展或迁移至现代框架。1. MirServer-Delphi 不是“怀旧玩具”而是可调试、可验证、可演进的传奇2服务端学习基线如果你在 GitHub 或老论坛里搜到MirServer-Delphi别急着双击 exe 运行——它不是一键开服的绿色包而是一套完整保留了传奇2服务端核心通信逻辑、状态机调度和数据库交互路径的 Delphi 源码工程。它用的是 Delphi 7兼容 D2007/D2010不依赖 .NET 或现代框架所有协议解析、客户端连接管理、地图同步、物品掉落规则都写在.pas文件里函数命名直白如TGameServer.OnRecvLoginReq、状态流转清晰stLoginWait → stGameReady。对想真正理解 MMORPG 服务端如何从 TCP 包解析出角色登录请求、如何把MovePacket转成地图广播、如何用TDataSet封装 SQL 操作数据库的开发者来说这套代码比任何文档都真实。它适合三类人刚接触服务端开发的 Delphi 初学者能跑通即见成果、想逆向分析传奇2协议的协议研究者结构体定义全在Const.pas和Protocol.pas、以及需要快速搭建私有测试环境做功能验证的运维/测试工程师。注意它不提供 Web 管理后台、不内置 HTTPS、不支持跨平台编译——它的价值不在“开箱即用”而在“每一行都能打断点、改逻辑、加日志”。2. 用 Delphi 7 编译 MirServer-Delphi 的最小可行环境与关键配置项2.1 环境准备为什么必须是 Delphi 7而非 Community Edition 或 12.xMirServer-Delphi 项目大量使用TMemoryStream.ReadBuffer的非标准偏移用法、TStringList.LoadFromFile的 ANSI 编码隐式处理、以及TIdTCPServer组件的特定事件绑定顺序如OnConnect必须在OnExecute前触发。Delphi Community Edition 默认启用 Unicode 字符串PChar与AnsiString混用会导致LoginPacket解析时AccountName字段截断Delphi 12.3 引入的{$IFDEF AUTOREFCOUNT}编译指令会破坏TUserSession对象的引用计数释放逻辑造成内存泄漏。实测可用组合为Windows 10/11 Delphi 7 Update Pack 2Build 10.0.1956 Indy 9.0.18。安装时需禁用 Windows Defender 实时防护因其会拦截MirServer.exe的CreateThread调用并手动将Indy9\Lib\Core目录加入 Delphi 的 Library Path。提示不要尝试用dcc32.exe命令行编译。该项目包含 17 个.dpk包如MirDB.dpk,MirNet.dpk必须通过 IDE 的Component → Install Package逐个注册否则TDBConnection组件在窗体设计器中无法拖拽。2.2 编译前必改的 4 个核心单元文件2.2.1 修改Const.pas中的服务器地址与端口常量// 原始代码硬编码本地回环 const SERVER_IP 127.0.0.1; SERVER_PORT 5600; DB_SERVER 127.0.0.1; DB_NAME mir2;需改为可配置形式避免每次修改都要重新编译// 修改后从 mirserver.ini 读取 uses IniFiles; var Ini: TIniFile; begin Ini : TIniFile.Create(ExtractFilePath(ParamStr(0)) mirserver.ini); try SERVER_IP : Ini.ReadString(Network, BindIP, 0.0.0.0); // 改为 0.0.0.0 允许外网连接 SERVER_PORT : Ini.ReadInteger(Network, Port, 5600); DB_SERVER : Ini.ReadString(Database, Host, 127.0.0.1); DB_NAME : Ini.ReadString(Database, Name, mir2); finally Ini.Free; end; end.2.2.2 修复DBConn.pas中的 SQLite 中文乱码问题Delphi 7 默认TADOConnection使用gb2312编码但传奇2客户端发送的VARCHAR字段如角色名、帮派名实际为GBK编码。直接ExecSQL(INSERT INTO CHARACTERS (Name) VALUES (张三))会导致数据库存入乱码。解决方案是在连接字符串中显式指定编码// 原连接字符串错误 ConnectionString : ProviderSQLOLEDB.1;Data Source DB_SERVER ;Initial Catalog DB_NAME ;User IDsa;Password123;; // 正确写法强制使用 GBK 编码层 ConnectionString : ProviderSQLOLEDB.1;Data Source DB_SERVER ;Initial Catalog DB_NAME ;User IDsa;Password123; CharsetGBK;; // 关键参数Delphi 7 ADO 驱动支持此扩展2.2.3 调整Protocol.pas中的包长校验逻辑传奇2 客户端发送的LoginPacket固定为 32 字节但部分模拟器如 Mir2Emu会发送 33 字节末尾多一个\0。原始代码if Length(Buffer) 32 then Exit;会导致连接被拒绝。应改为宽松校验// 原逻辑 if Length(Buffer) 32 then Exit; // 修改为允许 32~33 字节且第33字节为 0 才忽略 if (Length(Buffer) 32) or (Length(Buffer) 33) then Exit; if (Length(Buffer) 33) and (Buffer[33] 0) then Exit;2.2.4 替换Log.pas中的文件写入方式防止锁死原日志模块使用AssignFile(F); Rewrite(F)直接写文件在高并发下易因文件句柄未释放导致Access Denied错误。改为TFileStreamTStringList.SaveToFileprocedure WriteLog(const Msg: string); var FS: TFileStream; SL: TStringList; begin SL : TStringList.Create; try SL.Add(FormatDateTime(yyyy-mm-dd hh:nn:ss, Now) Msg); FS : TFileStream.Create(log\server.log, fmOpenWrite or fmShareDenyWrite); try FS.Seek(0, soEnd); SL.SaveToStream(FS); FS.Write(#13#10, 2); // 手动写换行 finally FS.Free; end; finally SL.Free; end; end;2.3 编译与启动验证流程步骤操作命令/动作验证要点常见失败原因1在 Delphi 7 IDE 中打开MirServer.dpr左下角状态栏显示Project MirServer: Loaded.dpr文件路径含中文字符需全英文路径2Project → Options → Compiler → Runtime Errors取消勾选Range checking和I/O checking编译时无Range check error报错Delphi 7 默认开启范围检查Buffer[255]访问会中断3Build → Build MirServer输出目录生成MirServer.exe和mirserver.iniIndy9包未正确安装报错Unit IdGlobal not found4双击MirServer.exe观察控制台窗口显示Server started on port 5600且无Access violationmirserver.ini中DB_SERVER指向不存在的 SQL Server 实例成功启动后控制台会持续打印Accepting connections...此时用telnet 127.0.0.1 5600应返回Connected to host—— 这证明 TCP 监听已就绪后续可接入官方客户端或 Wireshark 抓包验证协议交互。3. 连接 MySQL 数据库的完整配置与字段映射对照表3.1 创建兼容的 MySQL 数据库结构MirServer-Delphi 默认适配 SQL Server但生产环境更常用 MySQL。需执行以下建库脚本MySQL 5.7CREATE DATABASE mir2 CHARACTER SET gbk COLLATE gbk_chinese_ci; USE mir2; CREATE TABLE characters ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(32) CHARACTER SET gbk NOT NULL DEFAULT , level smallint(6) NOT NULL DEFAULT 1, exp bigint(20) NOT NULL DEFAULT 0, gold int(11) NOT NULL DEFAULT 0, map tinyint(4) NOT NULL DEFAULT 0, x smallint(6) NOT NULL DEFAULT 0, y smallint(6) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY name (name) ) ENGINEInnoDB DEFAULT CHARSETgbk; -- 注意此处 charset 必须为 gbk而非 utf8mb4 -- 因为 Delphi 7 的 ADO 组件无法正确处理 UTF-8 多字节字符注意不要使用utf8mb4字符集。Delphi 7 的TADOConnection在CharsetGBK模式下若数据库用utf8mb4会导致SELECT name FROM characters返回空字符串。gbk_chinese_ci是唯一经实测兼容的排序规则。3.2 修改DBConn.pas以适配 MySQL ODBC 驱动// 原 SQL Server 连接字符串 ConnectionString : ProviderSQLOLEDB.1;Data Source DB_SERVER ;Initial Catalog DB_NAME ;User IDsa;Password123;CharsetGBK;; // 改为 MySQL ODBC 连接需提前安装 MySQL ODBC 5.3 Unicode Driver ConnectionString : Driver{MySQL ODBC 5.3 Unicode Driver}; Server DB_SERVER ; Database DB_NAME ; User DB_USER ; Password DB_PASS ; Option3;Charsetgbk;; // 关键Charsetgbk不是 GBK3.3 核心数据表字段与 Delphi 对象属性映射关系MySQL 字段名Delphi 单元文件对应 Pascal 类型业务含义特殊处理说明nameTCharacterRecord.NameCharRec.passtring[32]角色名称输入时需Trim()去空格否则SELECT * FROM characters WHERE name张三 无法匹配levelTCharacterRecord.LevelWord当前等级传奇2等级上限为 355Word足够0~65535expTCharacterRecord.ExpInt64经验值必须用Int64355级所需经验超Integer范围约 2^31goldTCharacterRecord.GoldInteger金币数量无符号整数但 Delphi 7 无UInt32用Integer并逻辑限制 0为非法mapTCharacterRecord.MapIndexByte地图编号对应MapInfo.pas中MAP_INDEX_WELCOME 0等常量x,yTCharacterRecord.PosX,PosYSmallInt坐标位置范围 -32768~32767传奇2地图尺寸最大为 256×256完全覆盖3.4 测试数据库连通性的最小代码片段在MainForm.pas的btnTestDBClick事件中添加procedure TMainForm.btnTestDBClick(Sender: TObject); var Q: TADOQuery; S: string; begin Q : TADOQuery.Create(nil); try Q.Connection : DBConn; // 全局数据库连接对象 Q.SQL.Text : SELECT COUNT(*) FROM characters; Q.Open; S : Format(数据库正常共 %d 个角色, [Q.Fields[0].AsInteger]); ShowMessage(S); except on E: Exception do ShowMessage(数据库连接失败 E.Message); end; Q.Free; end;运行后点击按钮若弹出数据库正常共 0 个角色说明 MySQL 连接、查询、编码全部通过。此时可导入官方characters.sql初始化数据。4. 调试登录流程从 TCP 握手到角色数据加载的 7 个断点位置4.1 客户端连接建立阶段的关键断点当telnet 127.0.0.1 5600成功时服务端会在MirNet.pas的TGameServer.OnConnect事件中触发。在此处设置断点procedure TGameServer.OnConnect(AContext: TIdContext); begin // 断点 1确认连接上下文创建成功 Log(New connection from AContext.Connection.Socket.Binding.PeerIP); // 断点 2检查客户端 IP 是否在白名单如有 if not IsIPAllowed(AContext.Connection.Socket.Binding.PeerIP) then begin AContext.Connection.Disconnect; // 主动断开 Exit; end; // 断点 3初始化会话对象 AContext.Data : TUserSession.Create; // 内存分配点关注是否 OOM end;提示AContext.Data是存储用户会话的唯一容器所有后续逻辑如OnReceive都通过它访问TUserSession实例。若此处Create失败OnReceive中调用TUserSession(AContext.Data).LoginState会引发Access violation。4.2 登录包解析与验证的 4 个核心断点登录流程按顺序触发以下断点4.2.1 断点 4OnReceive中捕获原始字节流procedure TGameServer.OnReceive(AContext: TIdContext; AInput: TStream); var Buffer: TBytes; Len: Integer; begin Len : AInput.Size; SetLength(Buffer, Len); AInput.Read(Buffer[0], Len); // 断点 4在此查看 Buffer[0..31] 是否为合法 LoginPacket // 正常登录包前 4 字节为 $01,$00,$00,$00包类型 长度 if (Len 4) and (Buffer[0] $01) then ParseLoginPacket(AContext, Buffer); end;4.2.2 断点 5ParseLoginPacket中解包账号密码procedure TGameServer.ParseLoginPacket(AContext: TIdContext; const Buffer: TBytes); var Account: string; Password: string; begin // 断点 5提取账号Buffer[4..19]16字节GB2312 编码 SetLength(Account, 16); Move(Buffer[4], Pointer(Account)^, 16); Account : Trim(Account); // 去除末尾 \0 // 断点 6提取密码Buffer[20..35]16字节MD5 加密 SetLength(Password, 16); Move(Buffer[20], Pointer(Password)^, 16); // 断点 7调用数据库验证此处进入 DBConn.pas if ValidateUser(Account, Password) then SendLoginSuccess(AContext) else SendLoginFail(AContext); end;4.3 验证失败时的排错路径若ValidateUser返回False按以下顺序检查数据库查询语句在DBConn.pas的ValidateUser函数中Q.SQL.Text : SELECT password FROM accounts WHERE account Account —— 确认Account变量值是否含不可见字符如#13#10可用ShowMessage(Ord(Account[1]))查看 ASCII 码密码比对逻辑传奇2 密码为 16 字节 MD5 二进制值非 Base64 或 Hex 字符串。if Q.Fields[0].AsBytes Password then必须用AsBytes获取原始字节不能用AsString字符集转换若数据库中account字段存为utf8mb4Q.Fields[0].AsString会返回空此时需改用Q.Fields[0].AsAnsiString并确保连接字符串含Charsetgbk。5. 优化客户端重连与地图广播性能的 3 个关键参数调整5.1 调整TIdTCPServer的连接队列深度防止丢包默认TIdTCPServer的MaxConnections为 100但在高并发登录场景下如 200 人同时点击登录新连接请求会被内核丢弃。需在MainForm.pas的FormCreate中显式设置procedure TMainForm.FormCreate(Sender: TObject); begin GameServer : TGameServer.Create; // 断点 8增大监听队列长度Linux 为 somaxconnWindows 为 BackLog GameServer.TcpServer.ListenQueue : 512; // 从默认 100 提升至 512 GameServer.TcpServer.MaxConnections : 2000; // 同时在线上限 GameServer.Active : True; end;ListenQueue参数直接影响 TCP 三次握手完成后的连接等待队列长度。值过小128会导致telnet连接时出现Connection refused过大1024则消耗更多内核内存需根据服务器内存调整。5.2 控制地图广播频率避免 CPU 过载原始代码中每个玩家移动都会触发BroadcastMovePacket向同地图所有玩家发送 12 字节包。当 50 人在同一地图时单次移动产生 50×12600 字节广播每秒 10 次即 6KB/sCPU 占用率达 30%。优化方案是合并广播// 在 MapManager.pas 中添加延迟广播队列 type TMoveBroadcastItem record MapID: Byte; Data: TBytes; // 12 字节 MovePacket end; var MoveBroadcastQueue: TList; // 存储 TMoveBroadcastItem // 每 100ms 批量发送一次而非实时 procedure TMapManager.FlushMoveBroadcast; var I: Integer; Item: TMoveBroadcastItem; begin for I : 0 to MoveBroadcastQueue.Count - 1 do begin Item : PMoveBroadcastItem(MoveBroadcastQueue[I])^; BroadcastToMap(Item.MapID, Item.Data); end; MoveBroadcastQueue.Clear; end;将BroadcastMovePacket调用替换为MoveBroadcastQueue.Add(Item)并在TTimer的OnTimer事件中每 100ms 调用FlushMoveBroadcast。实测可降低 CPU 占用 18%且玩家移动流畅度无感知下降。5.3 设置客户端心跳超时时间防止僵尸连接传奇2 客户端默认每 30 秒发送一次PingPacket包类型$02。服务端需在OnExecute中检测超时// 在 TGameServer.OnExecute 中 procedure TGameServer.OnExecute(AContext: TIdContext); var Session: TUserSession; NowTime: TDateTime; begin Session : TUserSession(AContext.Data); NowTime : Now; // 断点 9若 60 秒未收到 Ping则断开 if (NowTime - Session.LastPingTime) * 24 * 60 * 60 60 then begin Log(Format(Client %s timeout, disconnecting, [Session.Account])); AContext.Connection.Disconnect; Exit; end; // ... 其他逻辑 end;Session.LastPingTime在OnReceive中解析到$02包时更新。60 秒阈值兼顾网络抖动避免误杀和资源回收及时释放内存。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价