艾尔之光单机端:KOM 算法 2 解密 + 修复记录(附工具包)
适用范围:绿色 MSSQL 版单机端,5 个服务进程(Center 9100 / Channel 9400 / Game 9300 / Global 9500 / Login 9200),客户端 `X2登录器.bat`(`X2.exe test test`)启动
客户端数据版本特点:客户端实际读取的是 `data001~data150.kom` 里的字节码/二进制,磁盘上的 ClientScript 明文 Lua 很多时候改了不起作用
一、这次到底做了什么
解密部分
1. 把 `KOM` 容器的 Algorithm 2(XOR+CRC 流解密 + zlib)完整逆出来,并写成 Python 工具。
2. 打通了客户端 Lua 的完整链路:
- KOM 提取 LuaJIT 字节码;
- luajit-decompiler-v2 反编译;
- 修改后可用 LuaJIT 2.0.2 重新编译,再转成 KOG 魔数 `1B 4B 4C` 格式回写 KOM。
3. 摸清了 `item.kim`(在 `data036.kom` 里,18.7MB)的 ID 存储方式:物品 ID 是 4 字节小端,可以直接定位/比对。
修复部分(摘要)
- 后台单账号时 JSON 不是数组导致列表空白 → 已修。
- 后台发物品邮件游戏里看不到 → `GPost.DelDate` 没写导致计算列 `Deleted=1` → 已修。
- 背包页签全乱/不显示 → 背包容量表存在非法 100 格 → 全表重写为合法扩容值。
- 新建角色“角色数据下载错误” → `gup_create_unit` 残缺,丢了 PVP/Spirit/背包容量等初始化 → 已恢复完整版。
- 宠物包双击打不开 → 客户端 KOM 里的 `RandomItem.lua` 没有对应开箱表 → 用 LuaJIT 重编译回写 KOM。
- 官方新手支援箱 85000513 永远不显示 → 根因:客户端 `item.kim` 里根本没有 85000513,改服务端 Item.lua 无济于事 → 换成客户端存在的可见替代品。
- 自制新手礼包(方案 A:复用现成礼包 ID + 自定义开箱组)→ 已实装,建号自动发。
- 在线时长活动 + 公测活动 → 已开启,并改造存储过程让活动奖励直接进背包(绕过客户端邮筒卡死)。
- 更多细节见包内《修端记录》和《工作进度交接》。
二、KOM 格式速查
文件头 `0x48` 字节:
- `0x44` 处是 XML 索引的 4 字节小端长度;
- XML 里每个 `<File>` 有 `Name / Size / CompressedSize / Algorithm / Checksum / FileTime`;
- 数据区从 `0x48 + xml_size` 开始,按 CompressedSize 顺序排列。
算法分布
| Algorithm | 处理方式 |
| --- | --- |
| 0 | 直接是 zlib 压缩数据,解压即可 |
| 1 | Blowfish ECB,key = `gpfrpdlxm`,只解密 8 字节对齐前缀 |
| 2 | XOR+CRC 流解密,再 zlib 解压 |
算法 2 的关键点
- 种子:文件名小写(不含路径)+ CompressedSize 小端字节;
- 用 `KEY20`(5 个 32 位密钥异或 `0x38D2DFA4` 后小端拼接)算 CRC 种子;
- CRC 表是标准反射多项式 `0xEDB88320`;
- 解密流与标准 CRC32 交织,每字节解出的值同时推进 CRC 状态;
- 解完流之后再做 zlib inflate(`wbits=15/-15/47` 都试)。
回写(重建 KOM)
1. 明文 zlib compress;
2. 做一次解密流的逆运算得到密文;
3. `Checksum = adler32(密文)`;
4. 更新 XML 里的 Size/CompressedSize/Checksum;
5. 头 + 新 XML + 所有条目密文拼接即可。
LuaJIT / KOG 字节码转换
- 标准 LuaJIT 2.0.2 头:`1B 4C 4A vv`
- KOG 头:`1B 4B 4C (vv | 0x80)`
- 编译时注意用 LuaJIT 2.0.2,不要用 5.1/5.2 的 luac。
三、工具包说明
包内目录:
```
论坛发布包/
├─ 解密与修复记录.md
├─ 使用说明_先看这个.txt
├─ 01_工具/
│ ├─ KOM_算法2解密.py # 解包/列表/提取/算法2加解密函数
│ ├─ KOM_
辅助_查询提取.py # list / find / extract 三个子命令
│ ├─ luajit-decompiler-v2.exe # LuaJIT 字节码反编译
│ └─ KOM_回写替换示例.py # 替换单个条目字节并重建 KOM 的实战脚本
├─ 02_文档/
│ ├─ KOM格式与算法速查.md
│ ├─ 常用命令.md
│ ├─ 修端记录_20260815.md
│ ├─ EventData_活动清单_中文翻译_20260815.txt
│ ├─ 在线活动可行性_20260815.txt
│ ├─ 候选礼包箱_20260815.txt
│ └─ 艾拉前期装备候选_20260815.txt
├─ 03_数据库补丁SQL/
│ ├─ gup_create_unit_def_full_restore_quest_reward_20260815.sql
│ ├─ gup_create_unit_def_newbie_gift_130508_20260815.sql
│ ├─ gup_create_unit_def_valid_slots_patched_20260815_1910.sql
│ ├─ gup_insert_post_item_def_before_direct_events_20260815.sql
│ └─ gup_insert_post_item_direct_event_reward_20260815.sql
└─ 05_服务器启动脚本示例/
├─ 一键启动.bat
└─ 一键停止.bat
```
依赖:Python 3(算法 1 需要 `pycryptodome`,算法 0/2 无第三方依赖)。
四、修复里最有参考价值的几个坑
1. 客户端优先读 KOM,不读磁盘 Lua。
改 `ClientScript` 磁盘文件没反应时,先查这个脚本到底在哪个 KOM、哪个条目、什么算法。
2. 物品显示不出来的第一步不是查服务端,是查 `item.kim`。
85000513 就是典型:服务端、RandomItemTable、RewardTable、图标全都“齐”,但客户端 item.kim 没有这条记录,无论放哪个背包页都不可能显示。
3. RandomItem 开箱组的概率总和不能超过 100。
`AddRandomItemGroup` 每项 100% 会导致后续条目全部加载失败。礼包类 `m_bGiveAll = True` 会把组内条目全发,概率项只是权重校验,所以“每项 5% × 20 项”是合法的整组全给写法。
4. EventInfo 的 OnOff 不一定可靠,EndDate 过滤才可靠。
关闭官方新手活动时,`OnOff=0` 之后服务器仍然安装过该事件;把 `EndDate` 改成过去时间后,重启才彻底不再安装。
5. 邮件奖励在这版客户端基本是坑。
村子邮筒点击挂死(`VILLAGE_POSTBOX.X`);换模型、换入口、重编译菜单都试过,未彻底解决。现在活动奖励走“直接进背包”的存储过程改造。
6. 建号过程必须完整。
`gup_create_unit` 缺 GUnitPVP_Season2 / GSpirit / GItemInventorySize 等初始化时,角色能建出来,但进游戏直接“角色数据下载错误”。
五、当前状态 / 已知未解决
- 村子邮筒/邮箱客户端挂死:未解决(当前绕过方案是活动与后台奖励直接进背包)。
- 官方新手箱 85000513~85000518:客户端 item.kim 全部缺失,官方活动已停用,用自制礼包替代。
- 连续登录、签到类在线活动:奖励物品在 item.kim 里缺失,暂未开启。
- 除艾拉外,其他职业建号基础装备还是科宝套(可按同样方法换成各职业 Space Ruler)。
- 事件活动只开了一部分(每日在线 14-22、公测 117),其余可按 EventData 清单继续开。
六、免责声明
- 本包只包含自己写的工具、文档、SQL 补丁和修改说明,不包含客户端 KOM、服务端程序或任何商业资源。
- 仅供私人服务器技术交流与学习研究,请勿用于商业用途,资源版权归原公司所有。
- 环境不同时请先备份再动手;SQL 与 Lua 改动建议先在测试角色上验证。