本帖最后由 烟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界面效果图
欢迎学习《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;
}
}
https://github.com/xingshen60771/PFPExtractetor/
|