吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1193|回复: 20
收起左侧

[其他原创] 【课后作业答案】经典解谜单机小游戏《梦之旅系列》的PFP文件解压工具源代码

[复制链接]
烟99 发表于 2024-11-1 19:29
本帖最后由 烟99 于 2024-11-1 21:11 编辑

大家好!在那天的我讲的关于某国产游戏的PCK算法分析你学会了吗?课后作业做了吗?今天揭晓课后作业参考答案。
文件格式大致分为四部分:


1、第一部分:包文件的头部,用于识别是这种格式的包文件,占用4字节且字节内容固定,为:0x50 0x46 0x50 0x4B
2、第二部分:包内的文件数量,占用4字节,将这4个拼接成一个十六进制值就是它的真实文件数量。
3、第三部分:包文件的目录表,里面记录了文件绝对路径的大小,占用一个字节,因此这意味着文件名最长不能超过FFh(即255个字节);紧跟其后是文件的绝对路径,占用若干字节,紧跟其后是文件偏移,占用4个字节;紧跟其后是文件大小,占用4个字节。
4、第四部分:包文件的数据区,将所有文件拼接在一起形成的区域。

你答对了吗?
本工具理论上讲只要是基于PlaygroundSDK开发的游戏里的PFP文件都可以解开,我只测试了梦之旅五部曲和极乐浪子共六款游戏都没有问题,其他游戏请自行尝试。另外PlaygroundSDK 开发的游戏支持加载解包后的内容,所以不需要考虑如何把文件压回去。


工具GUI界面效果图

微信图片_20241101193046.png

《PFPExtractetor》软件源代码

欢迎学习《PFPExtractetor》软件源代码!本源码遵循Creative Commons Attribution - NonCommercial许可协议,你可以在保留源码署名并保证不用于商业用途的前提下自由的使用、分发、修改本源码,但坚决不允许用于商业用途!   

基本信息

源码名称:PFPExtractetor
源码版本:1.0.0
源码作者:52pojie.cn
遵循协议:Creative Commons Attribution - NonCommercial
源码语言:C#
.NET Framework框架版本:4.5  

基本介绍

本工具用于演示PFP格式的游戏资源文件的解压缩功能,理论上讲可以解压缩游戏厂商PlayFirst开发或发行所有格式为PFP的游戏资源包文件,同时基于该厂商的PlaygroundSDK开发的其他游戏也可解压。本软件演示了PFP文件算法的文件偏移、大小、文件目录信息的存储方式和数据区的内容存储规则,对研究资源逆向基础提供了重要参考资料。  

源码更新日志

"--------------------------------------------
2024.11.01
"--------------------------------------------
源码对外公开  

如何编译

本软件使用了Tuple多元List,因此依赖于.NET Framework V4.5运行,原则上Visual Studio 2015就可以编译,但是本人是在Visual Studio 2022中编译的,因此建议在Visual Studio 2022中编译。  

郑重声明

1、本工具为以源码的形式发布,软件源码版权归吾爱破解所有,未经许可,禁止擅自编译发布!转载请注明出处!
2、本工具仅用于研究和讨论游戏文件存储技术,游戏资源文件归游戏开发商所有,切勿用于商业用途,否则源码作者不承担连带责任!
3、本源码遵循Creative Commons Attribution - NonCommercial许可协议,你可以在保留源码署名并保证不用于商业用途的前提下自由的使用、分发、修改本源码,但坚决不允许用于商业用途。  

关键源码展示



[C#] 纯文本查看 复制代码
        /// <summary>
        /// <List> 取PFP内部文件列表
        /// <param name="pfpfilePath">(文本型 欲获取内部文件列表的PFP文件) </param>
        /// <returns><para>成功返回PFP文件的单个文件的目录长度、完整文件路径、、文件偏移、实际大小、压缩大小,并封装在 <List>中</para></returns>
        /// </summary>
        public static List<Tuple<int, string, long, long>> GetPFPInformation(string pfpfilePath)
        {
            List<Tuple<int, string, long, long>> pfpList = new List<Tuple<int, string, long, long>>();

            using (FileStream fs = new FileStream(pfpfilePath, FileMode.Open))
            {
                // 检查是否为PFP文件,如果不是,抛出异常
                byte[] fileHead = { 0x50, 0x46, 0x50, 0x4B };                            //PFP文件头“PFPK”的Hex字节集数组
                byte[] checkHead = new byte[fileHead.Length];                            //欲打开的PFP文件头
                // 读入PFP文件头
                fs.Read(checkHead, 0, fileHead.Length);
                Console.WriteLine(checkHead.SequenceEqual(fileHead));
                // 若两边的字节集数组内容不一样或者长度不一样则视为不是PFP文件,并抛出异常
                if (!checkHead.SequenceEqual(fileHead) || fileHead.Length != checkHead.Length)
                {

                    throw new Exception("The file\"" + pfpfilePath + "\" is not a valid PFP format file!");
                }


                // 头部未发现异常,开始解析文件列表

                //取文件个数
                byte[] fileCountBit = new byte[4];      // 字节集状态下的数据区物理大小数值
                long fileCount;                         // 长整型的数据区物理大小数值
                // 定位到文件的0004h
                fs.Seek(4, SeekOrigin.Begin);
                // 取PFP内部文件数量信息
                fs.Read(fileCountBit, 0, fileCountBit.Length);
                // 将取到的pfp内部文件信息补满八字节,然后转换成长整型数值
                fileCount = BitConverter.ToInt64(PublicFunction.EightByteConverter(fileCountBit), 0);
                Console.WriteLine("PFP文件数量为:" + fileCount.ToString());
                Console.WriteLine("获取完数量的偏移为:" + fs.Position.ToString("X"));

                // 创建字节偏移记录,并将文件流移到08h处
                long byteOffset = fs.Position;
                fs.Seek(byteOffset, SeekOrigin.Begin);
                Console.WriteLine("开始读取文件列表时候的偏移为:" + fs.Position.ToString("X"));

                // 开始读取文件
                for (int i = 0; i < fileCount; i++)
                {
                    // 取目录长度
                    byte[] flieNameLenghBit = new byte[1];
                    fs.Read(flieNameLenghBit, 0, 1);
                    int flieNameLengh = BitConverter.ToInt32(PublicFunction.EightByteConverter(flieNameLenghBit), 0);
                    //偏移移动一位
                    byteOffset += 1;
                    fs.Seek(byteOffset, SeekOrigin.Begin);

                    // 取文件名
                    byte[] flieNameBit = new byte[flieNameLengh];
                    fs.Read(flieNameBit, 0, flieNameLengh);
                    string flieName = Encoding.Default.GetString(flieNameBit);
                    //偏移移动文件名长度位数
                    byteOffset += flieNameLengh;
                    fs.Seek(byteOffset, SeekOrigin.Begin);

                    // 取文件偏移
                    byte[] fileOffsetBit = new byte[4];
                    fs.Read(fileOffsetBit, 0, fileOffsetBit.Length);
                    // 四字节补到八字节,再转换成长整型数值,
                    long fileOffset = BitConverter.ToInt64(PublicFunction.EightByteConverter(fileOffsetBit), 0);
                    //偏移四个字节
                    byteOffset += 4;
                    fs.Seek(byteOffset, SeekOrigin.Begin);

                    // 取文件大小
                    byte[] fileSizehBit = new byte[4];
                    // 四字节补到八字节,再转换成长整型数值,
                    fs.Read(fileSizehBit, 0, fileSizehBit.Length);
                    long fileSizeBit = BitConverter.ToInt64(PublicFunction.EightByteConverter(fileSizehBit), 0);
                    //偏移四个字节
                    byteOffset += 4;
                    fs.Seek(byteOffset, SeekOrigin.Begin);

                    // 调试输出此文件信息
                    Console.WriteLine("-------------------\n" +
                                        "读取第" + (i + 1) + "文件成功!\n" +
                                        "===================\n" +
                                        "文件名长度:" + flieNameLengh.ToString() + "\n" +
                                        "文件名称:" + flieName + "\n" +
                                        "文件偏移:0x" + fileOffset.ToString("X") + "\n" +
                                        "文件大小:" + fileSizeBit.ToString() + "字节\n" +
                                        "个文件后的偏移" + fs.Position.ToString("X") + "\n" +
                                        "-------------------\n\n");

                    // 加入到四元List
                    pfpList.Add(Tuple.Create(flieNameLengh, flieName, fileOffset, fileSizeBit));
                }
                // 返回四元List
                return pfpList;
            }
        }


因篇幅原因,只展示文件列表的获取,解压操作略,请到GitHub中查看完整源码。

意见或建议

可通过论坛回帖留言的方式反馈,也可私信该帖楼主也就是我来反馈。禁止留QQ、微信等联系方式,对利用私信留联系方式的行为将从重处罚!
https://www.52pojie.cn/thread-1977704-1-1.html

源码链接

暂时隐藏,五天后开放。



https://github.com/xingshen60771/PFPExtractetor/



免费评分

参与人数 4威望 +1 吾爱币 +23 热心值 +4 收起 理由
fengxx5204 + 1 + 1 热心回复!
侃遍天下无二人 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
helh0275 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
lslan + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 烟99 发表于 2024-11-1 23:21
LuoShang 发表于 2024-11-1 22:58
大佬我想问一下,如果是apk有壳,他里面的pak包会不会也有壳,会影响解压吗

你说的壳我不知道是不是说的是加密,如果有加密就要先解开加密,比较低级的加密比如异或运算,举个最常见的例子吧,植物大战僵尸,这个游戏的pak文件就是先把文件整体字节集读入,然后和十六进制F7h进行异或运算,运算后的字节集才是明文的,但是还没完,根据现有资料显示,他那个pak和这个差不多,也是前半部分是目录区,后半部分是数据区,目录区也是用一个字节表示文件名长度,也是占用若干字节记录文件名信息,后面也是记录文件大小,但是没有记录文件偏移,而且文件大小后面的八个字节没搞清楚是干什么的,然后填充了一个00才是下一个文件是信息块,所以植物大战僵尸的pak不能通过文件偏移来定位文件数据,只能自己写变量一点点的加
LuoShang 发表于 2024-11-2 12:36
本帖最后由 LuoShang 于 2024-11-2 12:38 编辑
烟99 发表于 2024-11-1 23:21
你说的壳我不知道是不是说的是加密,如果有加密就要先解开加密,比较低级的加密比如异或运算,举个最常见 ...

我在其他地方找到了植物大战僵尸的pak解包软件,是可以正常使用的,那个apk我查壳是爱加密的,这种是不是很麻烦,我尝试直接丢进那个pak解包软件,是解不出来的

点评

他加密的是程序文件,pak文件是资源文件,和壳没有关系  详情 回复 发表于 2024-11-2 13:29
yuntai 发表于 2024-11-1 19:55
geminiqi 发表于 2024-11-1 21:25
谢谢大佬分享!
szypptz1qdfp 发表于 2024-11-1 22:51
哇哦,感谢感谢分享
LuoShang 发表于 2024-11-1 22:58
大佬我想问一下,如果是apk有壳,他里面的pak包会不会也有壳,会影响解压吗

点评

你说的壳我不知道是不是说的是加密,如果有加密就要先解开加密,比较低级的加密比如异或运算,举个最常见的例子吧,植物大战僵尸,这个游戏的pak文件就是先把文件整体字节集读入,然后和十六进制F7h进行异或运算,运  详情 回复 发表于 2024-11-1 23:21
nicksean 发表于 2024-11-2 09:28
谢谢大佬分享!
sfgcc 发表于 2024-11-2 12:16
看到了,回复表示尊重。
 楼主| 烟99 发表于 2024-11-2 13:29
LuoShang 发表于 2024-11-2 12:36
我在其他地方找到了植物大战僵尸的pak解包软件,是可以正常使用的,那个apk我查壳是爱加密的,这种是不是 ...

他加密的是程序文件,pak文件是资源文件,和壳没有关系
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 05:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表