首页
论坛
课程
招聘
雪    币: 439
活跃值: 活跃值 (29)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝

[调试逆向] [原创]仙剑3剧情控制码解析和cpk格式分析

2012-10-14 03:40 17114

[调试逆向] [原创]仙剑3剧情控制码解析和cpk格式分析

2012-10-14 03:40
17114
上次发了个编译pyside的帖子拿了个精怪不好意思的, 翻了翻硬盘决定发点存货出来.
存货之一, 仙剑的sce反编译和编译工具, 叫做SceCompiler, 当然因为是坑掉的东西, 刚看了看居然还有Longkui拼写成longui问题, 我已经无法确认是整理官方头文件时候拷贝错误还是他夹杂的头文件确实是旧版错误的了.
顺便一提 没想到那时候的帖子还在
http://bbs.gameres.com/showthread.asp?threadid=2511

这个设计的初衷是能够反编译sce成半吊子的脚本, 然后再修改对话后编译的sce能够被引擎识别. 做到脚本和源文件一样那是不大可能的了, 但是sce一样那是可以的. 首先请看截图.


左侧是官方打包时候, 混在了游戏资源里面的策划资源. 同时混在里面的还有3ds的场景, 粒子系统参数说明的doc, 各种.bak等等等等.
右侧是从M23.cpk里面提取的m23.sce反编译后的结果, 当然注释部分是反不出来的… 还有一些循环结构也需要润色.
为啥要做这个东西的说来话长了, 我只记得雪见跳炉时候老子正在吃白吉馍, 一把攥的白吉馍的肥肉汁液飞溅, 马上读取存档, 第二次是龙葵妹妹跳了!!!
在妹子和妹妹之间, 当然我再次选择了龙葵妹妹, 千年之前就为了哥哥受苦, 起码这辈子一定要保护好, Load, 雪见又跳了一次, 可怜我还是第一次玩这种虐心的游戏, 以前一直打怪时候倒不觉得, 忽然少了一个人, 那种心中少了一块的感觉是怎么的一回事…
当时到寰宇之星论坛发了一通牢骚以后, 我也意识到要什么隐藏结局那是不可能的, 但是天无绝人之路, 有一次仙三打补丁打了一半崩溃了. 再启动游戏, 来个提示, lzo block错误什么的, 顿时我就跟打了鸡血一样兴奋了起来, 那时候正好所在的网站喜迎改版, 喝了一肚子啤酒滚回网站就开始打开WinHex, 大眼瞪小眼地想要把文件解出来.
怎么找到头绪的我也记不得了, 最后取得进展还是因为music.cpk和movie_end.cpk吧, 那时候有个工具叫做MediaRipper32吧, 是根据在流里面搜文件头部特征来提取文件的, 从这两个文件里面提取出mp3和bik以后, 顺利地从WinHex搜到了同样内容所在的地址, 再搜到了记录地址和大小的索引信息, 最后是把前后的看起来像是文件内部偏移的值都跳转一遍, 找到了文件名.
PS: 其实刚写帖子时候弄错了, 刚翻出了以前记下来的wps文件, 发现是靠FileMon这种原始的手段, 监控游戏读取文件时候都从什么位置, 读取了几个字节做为突破口的, 然后去WinHex里面把对应的数据复制出来分析.
具体读取的流程可以去看MainUnt.pas里面的OpenCpkThreadExecute这个函数, 这里大概简述一下.
  TVMBR = packed record
    Magic:     array[0..2] of Byte;//RST
    RevM:      Byte;  //$1A
    Rev1:      DWORD; //$01,00,00,00
    VFatOffset: DWORD;//$80
    EmbOffset: DWORD; //$00E00080
    Rev2:      DWORD; //00,80,00,00
    BlockNum:  DWORD; //记录数
    RevZ:      DWORD; //00000000
    RevVF1:    DWORD; //80
    BlockNum2: DWORD;
    Rev3:      DWORD;//00,80,00,00
    RevZ2:     DWORD;//0000000
    CpkSize:   DWORD;//整个Cpk长度
    RevNU:     array[0..$4F] of Byte;
  end;


这年头用Delphi的人已经不多了吧, packed record就是C里面的#pragma pack(1)的意思, 不过没有push和pop, 作用域自动限制在end范围内.
可以尝试用010Editor打开仙剑安装目录下scene\m23.cpk, 头部的128个字节, 就是一个VMBR记录了.
前四个字节是5253541A, 当然525354有什么具体含义不知道, 可能仅仅就是连起来的三个字母RST, 或者是ResourceTree->的意思, 正好1A是个箭头.
+4位置固定是1. +8位置是VMBR本身大小, 也可以解释为VFat开始的地址.
+8位置是E0080, 当时文档里面给他编号为”第二头部”… 解压时候没用到, 但是读取时候应该是利用这个做索引啥的.
+14位置是记录数, 这个记录是包含各种节点的数量, 文件/目录/已删除项目三类.
+2C是整个cpk文件的长度,  后面直到VFat开始的地方都为空.

然后来到文件+80的地方, 这里有对应个节点数目的TRecBlock记录.
  TRecBlock = packed record
    vID:      DWORD;	// 记录ID
    Rev2:     DWORD;	// 节点种类
    ParentvID: DWORD;	// 上级ID
    Offset:   DWORD;	// 偏移
    lzosize:  DWORD;	// 压缩后尺寸
    filesize: DWORD;	// 原始尺寸
    RevFnLen: Integer;	// 名称长度
    RecIdx:   DWORD;
  end;

其中最重要的是+0地方的VID和+8地方的ParentVID. 这是构成一个树状结构的办法之一, 想象一下以前的老论坛对就是001调试论坛这类的树状bbs, 本质上就是靠着根节点, 子节点, 子节点下的子节点形成一个树结构的. 而Rev2这里, 是做为标识节点是文件还是目录, 是否已删除用的.
0002,0001都是文件, 区别忘了貌似是一个是压缩一个是未压缩. 而0011是已删除的文件, 0003是目录. 或许也会有0013表示已删除的目录, 不过目前没有遇到.
这里为啥要放个”已删除文件”在cpk里面呢? 当年的硬盘速度可能较慢, 而cpk包挺大的, 仙剑3 的补丁程序在修改cpk时候, 没有完全的重组文件, 而是测试当前的原始文件压缩后, 是否还能放进原来位置, 放不进去的话, 就把原来位置删除, 并且在追加vfat数组中追加新的记录. 这个的前提当然是设计时候, vfat后面留了空间不需要推挤后面的文件记录, 而这些留下的”原来位置”, 在下一次补丁时候, 有较小的文件可以拿过来用 , 大概设计者的意图就是如此吧. 有了这两套信息, 我们就可以把cpk的内容解析出来了, 我做的是类似于资源管理器的界面, 左边文件夹数, 右边详细列表.

糟糕有点走题了, 想看完整实现的可以打开CPack_src_2004.rar和CPack_src_2012.rar, 前面是在一些基友手里发布过的版本, 只能用于D6/D7+KOL2.38这类不支持unicode的版本, 后面是写这个帖子时候, 发现编不过, 改了下Unicode兼容的部分的版本, 这个就只能Delphi2009以后用了.
回过头来说sce的文件组成, 当时对脚本系统了解的不多, 仔细想想看, 这个sce文件, 无疑就类似于现在的python的pyc, java的class, 和lua的字节码这类的东西, 伪编译过的字节码.
当然就当时这个工具的完成度来说, 与其说反编译, 还不如说是反汇编, 因为是顺序的解析, 控制流程和循环什么的都没有还原出来.
这个scecompiler的反汇编部分, 和大部分教学性质的反汇编引擎一样, 有一张指令格式表, 记录了指令的起始标识对应的助记符, 指令的宽度(参数数量), 后继字节的内容含义等等.
可以去看一下SceCompiler目录的CmdDefInit.inc文件, 里面定义了每个起始mcode对应的指令名称, 参数数量, 参数的类型信息. 这个文件就是在把所有的cpk解包以后, 找到的DecoderHelp.h里面的内容. 可能是因为软星在不断变更sce的格式, 所以每次生成sce时候, 都会生成当前的虚拟机指令定义, 以备出错时候调试.
提起sce的二进制格式, 既然都是一个公司的, 当然也类似于cpk的设计风格, 甚至可能是同一个人做的输出部分.
首先这个的VMBR记录很奇怪, 是7个字节.
TSceFileHeader=packed record
  Magic:Array[0..2] of Byte;	// SCE
  Rev1:WORD;//$1000
  BlockNum:WORD;//记录块数 
end;

这个记录里面重要的部分, 也就是BlockNum这个字段了, sce文件是由无数个block组成, 每一个Block, 对应着一个#scriptbegin和#scriptend之间的部分.
TSceBlockHeader=packed record
  BlockID:DWORD;
  Offset:DWORD;
  BlockName:Array [0..$3B] of Byte;
  Rev1:DWORD;
end;

文件头的末尾紧跟着的就是这无数个的Block索引信息, 在M23.sce里面是19个.
这个里面的Rev1貌似忘记做什么的了, BlockName无疑就是#scriptbegin 101,"海底城1层到2层"里面的名称部分. 而BlockID就是101这个数值.
Offset对应的是这个语句块对应的字节码所在的偏移, 这个的内容才是真正的字节码, 可以看MainUnt.pas的DeAsmSceFileExecute部分, 我在这里额外多存了一些无法确定但是在源文件里没有的字段, 用#DB方式写在了输出的文件中, 这是为了不去弄清楚这些字段, 就可以回写生成sce用的.
然后下面就是读取第一个字节, 从最开始提到的指令定义表取出参数数量, 循环处理每个参数, 根据参数类型, 打印到输出文件中. 有些数字值是代表常量的, 具体的转换可以看看ConstDefInit.inc里面的定义. 当然这里_longui应该是写成_LONGKUI. 不知道基于什么考虑, 软星使用的后缀是.asm, 大概是ActionScriptMacro?

整个游戏的脚本初始化是init.sce, 每个场景的初始化是场景虚拟目录下的场景名.sce文件, 另外对应还有一个.scs文件, 是和全局变量有关系的.

当然想把cpk和sce刚才提到的字段摸清楚, 还有一个办法, 就是分析仙剑3的exe, 不过当时exe是有那啥StarForce保护的, 而去除StarForce的免CD发布时候又被游侠加壳, 那时候看雪上面研究穿山甲的还不多, 待我脱掉的时候已经是工作有段时间, 开始进入加班期了... 还好当时没钻下去, 否则那时候那SP公司情况正紧, 要不是俺开着OD调试老板买来的没有源码的网关, 还不一定能撑过割接~~~虽然这公司后来买的网关继续没有源码, 最终还是没有撑过一次又一次的割接...

记得附件里的版本就差一个解析Goto的部分, 就可以自由编辑反编译后的每个语句块的剧情对话和条件了, 这个跳转是计算的偏移值, 也就是语句之间在文件中存储的位置差值, 给位置编号后应该就可以避免.
编译的时候也是分割各个参数, 然后转换成对应的流, 拼到语句块中, 不过当时基友劝我找个成熟的编译引擎, 但是我执意要自己实现, 关键这并不可怕可怕的是我想实现网上介绍的pascal支持的ifa=2thenbeginend这样的无分割也能正常编译的特性, 于是在Token匹配时候, 陷入了靠人眼图纸方式进行一堆顾前不顾后的检测, 来分隔出需要的参数, 最后这个函数bug越改越多, 彻底就坑了.
我现在看看当年写的代码, 已经无法明白设计的原理了, 大概有点类似于xml流式解析中, 位于什么标签, 跳出什么标签这类的模型, 可惜咱当时不懂有这类的办法, 现在要改还不如从头写或者干脆找解散的软星程序员去要来得快吧…
后记: 这个工具当年还没坑的时候, 尝试写过剧本, 也想着啊神秘的说书人一直暗中注视着主角, 看到主角将要失去妹子想起了自己当年的悲剧, 于是拦住雪见和龙葵, 自己蹭的一声跳进了铸剑炉中, 还想过因为龙葵和景天无法结合, 于是跳炉但是红葵已经设法绑定了2人的灵魂到景天体内, 最后景天和雪见生下一对双胞胎时候, 俨然就是红葵和蓝葵, 皆大欢喜皆大欢喜!!! 当然那时候还有个玩文曲星的朋友要写YY小说, 要写一个很强的角色想征用我的名字, 我毫不犹豫的说, 啊我要做悲剧人物, 当然这个悲剧人物的曾半仙真的悲剧了, 逆天失败, 存在都被天数抹削, 最后只在灯笼里面跟主角面授机宜后施施然湮灭, 当然这个小说现在也坑了, 而我也真的悲剧了, 各种给克扣工钱, 给翻脸赖账, 给代付违约金, 给医院骗去手术… 我有时候禁不住想, 是不是这些年其实是在游戏里面度过的, 结束后, 我摘下头盔, 其实自己还是一个坐在电脑前的无忧无虑的少年, 旁边老板拍了拍我的肩膀, : “兄弟, 快躲起来, 老师来网吧抄人了!”

[公告]看雪论坛2020激励机制上线了!多多参与讨论可以获得积分快速升级?

上传的附件:
最新回复 (37)
雪    币: 280
活跃值: 活跃值 (11)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
心如止境 活跃值 2 2012-10-14 03:45
2
0
抢沙发啊!!!!!!!!!!!!!
雪    币: 14949
活跃值: 活跃值 (1123)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
linhanshi 活跃值 2012-10-14 03:51
3
0
Support!
上传的附件:
雪    币: 280
活跃值: 活跃值 (11)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
心如止境 活跃值 2 2012-10-14 03:53
4
0
[QUOTE=linhanshi;1108972]Support!
[/QUOTE]

林版还不睡觉啊,小心身体。
雪    币: 102
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
哟哟哟哟 活跃值 2012-10-14 04:38
5
0
嘿嘿,Delphier,我骄傲
雪    币: 1
活跃值: 活跃值 (43)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lookzo 活跃值 2012-10-14 08:08
6
0
我擦,游戏资源解密,我完全不懂了,膜拜lz
雪    币: 439
活跃值: 活跃值 (29)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
曾半仙 活跃值 9 2012-10-14 08:31
7
0
1楼的附件更新了下加了个010Editor的CPack模版. sce模版做起来可能比较麻烦, 主要是想搞定编辑, 如果能够编辑对话字串时候自动修改所有跳转就好了. 看看下个周末是否能搞出来吧.
上传的附件:
雪    币: 378
活跃值: 活跃值 (18)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
小小的心 活跃值 2 2012-10-14 09:31
8
0
半仙又复活了。。。
雪    币: 3254
活跃值: 活跃值 (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yjd 活跃值 2012-10-14 09:50
9
0
强悍,支持。。
雪    币: 1034
活跃值: 活跃值 (21)
能力值: ( LV12,RANK:750 )
在线值:
发帖
回帖
粉丝
boywhp 活跃值 12 2012-10-14 09:52
10
0
我靠GameRes论坛,我以前经常混那里的vb板块的!!!好怀恋啊~~~~
雪    币: 134
活跃值: 活跃值 (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Fido 活跃值 2012-10-14 13:27
11
0
有意思的东东.....

同怀念GameRes...嘿嘿
雪    币: 73
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kingcomer 活跃值 2012-10-14 14:27
12
0
只能膜拜,不能学习
雪    币: 1579
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柔情似水 活跃值 2012-10-14 15:55
13
0
我靠,我这才发现牛人,半夜都不睡觉的。
雪    币: 965
活跃值: 活跃值 (751)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2012-10-15 19:51
14
0
喜欢看半仙的作品
雪    币: 258
活跃值: 活跃值 (81)
能力值: ( LV17,RANK:1820 )
在线值:
发帖
回帖
粉丝
riusksk 活跃值 41 2012-10-15 21:00
15
0
没玩过仙剑,文章也完全看不懂,只能膜拜了……
雪    币: 1635
活跃值: 活跃值 (15)
能力值: ( LV12,RANK:1000 )
在线值:
发帖
回帖
粉丝
天易love 活跃值 18 2012-10-15 22:32
16
0
快闪,老师来网吧抄人了!
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
善良屠夫 活跃值 2012-10-15 22:34
17
0
写的真好 技术好 还是文艺青年
雪    币: 404
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
moonspot 活跃值 2012-10-15 22:35
18
0
仙3玩N久了还没到头
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
善良屠夫 活跃值 2012-10-15 22:36
19
0
写的好 还是文艺青年  真心想知道没有天赋的人怎么才能炼成像这样的高手
雪    币: 439
活跃值: 活跃值 (29)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
曾半仙 活跃值 9 2012-10-15 23:43
20
0
天啊看雪老大也来了~~ 下次咱不玩文艺了, 来写个010Editor的模版的使用教程吧, 授人以鱼不如授人以渔啊
雪    币: 1147
活跃值: 活跃值 (30)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
yijun8354 活跃值 12 2012-10-16 00:37
21
0
最近爆发了哟
雪    币: 204
活跃值: 活跃值 (39)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
dkxzl 活跃值 1 2012-10-16 11:10
22
0
我是来顶最后一句话的
雪    币: 357
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
炎羽 活跃值 2012-10-16 12:17
23
0
看完我只想说技术宅改变世界
雪    币: 346
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
YwdxY 活跃值 2012-10-16 13:07
24
0
牛X,这也能搞定~
雪    币: 31
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zychy 活跃值 2012-10-16 15:07
25
0
围观!!!!
雪    币: 323
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhenly 活跃值 2012-10-16 15:36
26
0
哈哈,技术牛逼先不说,LZ最后一段写的太有才了……
玩过仙3的人都不会忘记那一段时光吧……
仰望45度……
雪    币: 284
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jerrynpc 活跃值 2012-10-16 16:28
27
0
怀念GmaeRes`````````
雪    币: 410
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
muyen 活跃值 2012-10-16 18:09
28
0
极好,也喜欢玩仙剑三,但是玩到这样去修改,做不到...写到哪个必须死一个时,当时我也觉得很落空,怎么少了一个人.......
雪    币: 1548
活跃值: 活跃值 (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
天涯一鸿 活跃值 2012-10-16 20:39
29
0
……我只想说,PAL3有完美结局啊……反倒是PAL4……我一直想把梦璃她妈说的把她许配给那个什么见鬼玩意的那段删掉……烦……
雪    币: 34
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yxgbo 活跃值 2012-10-17 09:29
30
0
mark mark mark
雪    币: 18
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ipfans 活跃值 2012-10-17 09:48
31
0
顶半仙
雪    币: 564
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lixupeng 活跃值 2012-10-19 20:35
32
0
学习有用想汉化游戏
雪    币: 439
活跃值: 活跃值 (29)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
曾半仙 活跃值 9 2012-10-20 07:32
33
0
卧槽被发现了
雪    币: 43
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Edj 活跃值 2012-12-24 00:37
34
0
仙剑的单机我喜欢
雪    币: 144
活跃值: 活跃值 (16)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
透明色 活跃值 2 2013-3-24 18:27
35
0
仙三 确实经典  ,就是 打仗起来 太可爱啦, 一点也兴奋不起来
雪    币: 439
活跃值: 活跃值 (29)
能力值: ( LV13,RANK:370 )
在线值:
发帖
回帖
粉丝
曾半仙 活跃值 9 2013-3-26 16:13
36
0
哼 你对着人家的小葵兴奋个啥
雪    币: 997
活跃值: 活跃值 (41)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
过林黑马 活跃值 2020-5-5 21:56
37
0
大佬如果能也弄下仙剑4的cpk就好了,现在仙剑4的cpk好像不像仙剑3那么好弄
雪    币: 256
活跃值: 活跃值 (141)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ZwCopyAll 活跃值 2020-5-6 13:53
38
0
mark
游客
登录 | 注册 方可回帖
返回