继续未完成的内容,声明
本文仅用于学习研究,不提供解压工具和实际代码。
由于时间仓促,剑3资源格式分析(仅用于学习和技术研究)(一)
大部分只是贴出了分析的结果,并没有详细的分析过程,比如:如何知道那是一个pak文件处理对象,
如何根据虚表偏移获取实际函数地址等等,这就需要读者对c++对象在内存中的layout有一定基础。
开始正文了~~,先整理下前面的分析结果:
1、剑3是通过package.ini 来管理pak文件的,最多可配置key从 0-32(0x20)的32个文件。
2、每个pak文件都用一个独立对象来管理,所有的pak对象指针存储在一个数组里(这个后面会用到)。
3、pak文件格式:[pak标记(Uint32)] + [文件数目(Uint32)]+[索引数据偏移(Uint32)]+未知内容。另外,每个文件的索引数据是16个字节。
一、路径名哈希
剑3的pak的内部文件是通过hash值来查找的,这样有利于加快查询速度。这就需要有一个函数通过传入 路径名返回hash值。
这个函数居然是导出的。。。g_FileNameHash
这个函数代码比较少,可以逆向出来用C重写,也可以直接使用引擎函数(LoadLibrary,GetProceAddress来使用)。
熟悉剑侠系列的朋友会发现,这个函数从新剑侠情缘(可能历史更久)开始就没有变过(确实没必要变),具体细节就不逐个分析了,我是写了一个单独的命令行工具
来测试的。
二、查询过程
查询函数在sub_10010E00
里,也就是(0x10010E00)的位置,我是通过简单分析g_IsFileExist 得知这个函数功能的。下面
来分析这个函数过程:
从前文可知,pak文件对象是存储在一个数组的这个数组是类似 KPakFile* m_szPakFile[0x21];
前面0x20个存储的都是KPakFile对象指针,最后一个存储的是数组长度。
这个搜索结构比较简单,就是遍历所有的KPakFile对象,逐个查询,找到了就返回。想知道具体怎么查询的吗,
接下来要看sub_100108B0了。
这个函数稍微有点长,分几个部分来分析吧:
首先,验证下Hash值是否是0,如果是0,肯定是错了:)
然后接着开始根据这个hash值进行查找了,经过分析,我发现这个函数其实是一个二分查找,代码贴出来如下 sub_10010320
:
从上面的代码还是比较容易可以知道,每个文件的16个索引数据中,前4个字节是hash值,这个函数返回的是这个文件是pak包的第几个。
接着前面的sub_100108B0
来看吧
这一段是保存查询到的数据到对象里。分析到这里,我只知道16个索引数据前4个字节是hash值,那么剩下的12个字节呢,
剩下的数据基本可以确定是:文件偏移、文件长度。我是个懒人,接下来的分析我是通过在fseek、fread下断点来得到的,为什么不是在SetFilePointer和ReadFile呢,
这是根据前面的分析得到的,因为pak文件管理对象使用的是C标准库函数。
根据fread和fseek的结果,可以得到如下结果:
索引数据构成是:
[哈希数值(Uint32)] + [文件偏移(Uint32)]+[未知数据(Uint32)] + 2(文件长度)+2(未知数据)。
剩下的,就是看看单独内部文件的解压方式了,
在fread的缓冲区上设置内存断点,就可以找到解压函数了:
sub_10018020
这个函数不算太长,一开始我也想逆向成C语言,后来看到如此多的分支就放弃了,转而用了一个偷懒的办法解决了:
从汇编代码可知这个函数的原型:
typedef int (*PUNPACK_FUN)(void* psrcData, int nSrcLen, void* pDstData, int* pDstLen);
直接加载剑3的dll,设置函数地址:
PUNPACK_FUN pEngineUnpack = (PUNPACK_FUN)((unsigned int)hEngineModule + 0x18020);
hEngineModule
是引擎dll的基址,大家看到了吧,dll的函数即使不导出,我们也是可以调用的:)
三、尾声
到这里,已经可以写出一个pak文件的解压包了,但是,我们还是没有还原真实的文件名,
下面是我解压的script.pak的文件的部分内容:
终于看到大侠们的签名了。当然,对着一堆hash值为名字的文件,阅读起来确实很困难,
那么有办法还原真实的文件名吗,办法还是有一些的,可以通过各种办法改写g_OpenFileInPak记录参数名,来获取游戏中用到的pak内部文件名,相信这难不倒各位了。
posted on 2010-07-16 20:47
feixuwu 阅读(4660)
评论(11) 编辑 收藏 引用 所属分类:
逆向工程