zhoujianbang 发表于 2025-12-12 23:57

分享一个PAK文件的内容,觉得很有意思分享一下

由于我是新人,还不太会发帖,可能图片和内容比较乱,大家凑合看,大神轻喷,我只是分享自己的学习经验:lol
分享一个某道的PAK文件,因为我比较怀念以前的日子,跟朋友一起杀星做副本,看到网上有很多SF,也放出来一些端,就研究了下,有很多工具要不不好用,要不一堆病毒,所以在各个地方查资料,研究pak文件格式,研究了一半了,感觉基本差不多了,所以发出来与大家共享,有能力的可以自己做。好,接下来是正题:
以服务端的gs的ETC为例使用010 Editor打开十六进制模式查看该文件显示看图1,接下来看这个文件的头部长度,一共有16字节图2如下:44 33 22 11 00 53 01 00 00 00 00 00 00 00 00 00其中44 33 22 11 前四字节为头部验证,后面的 00 53 01 00为这个文件的索引表大小转为十进制287454020,这个作为头部验证的数值,而索引大小为86784(注意,这个文件是小端序,所以转换时要注意高位和地位互转,也就是44332211在转换时高低位互换为11223344)剩下的8字节应该是保留字节,未使用,那么现在一个PAK文件头固定16字节,验证头4字节+索引大小4字节+保留区域8字节接下来是数据索引包含的信息占用64字节,20字节存储信息+名字44字节文件类型+文件数据在PAK中的偏移地址+压缩后的大小+原始大小+时间戳,这五个信息吗,每个都是4字节表示,接下来一个一个分析,先把第一个64字节复制过来00 00 00 00 10 53 01 00 4D 09 00 00 24 1B 00 0000 00 00 00 61 63 63 6F 75 6E 74 5F 71 75 65 7374 69 6F 6E 2E 6C 69 73 74 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00                                        :4字节        文件类型(0=文件, 1=文件夹)10 53 01 00                                        :4字节        文件数据在PAK中的偏移地址4D 09 00 00                                        :4字节        压缩后的大小24 1B 00 00                                        :4字节        原始大小00 00 00 00                                        :4字节        时间戳(为0则当前未使用)                                                        61 63 63 6F 75 6E 74 5F 71 75 65 7374 69 6F 6E 2E 6C 69 73 74 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00:44字节文件名(以空字符结尾)
已知的文件头索引表大小/单文件块大小=共有多少个文件!10 53 01 00偏移量转换后为15310
转到15310后开始读取第一个文件的内容。第一个字节为文件内容的标志位,与索引的标志类似,这里1为字面量(读取到什么就是什么),0为引用之前的内容。
第一行的内容为#FORM1 FORMAT接下来开始分析
第一个标志位为7F转为二进制01111111,同样小端序11111110,前七个字节为字面量最后为引用,所以读取两个字节,作为引用的偏移和长度使用。
前七个字节内容为#FORM1 (注意空格)到了FORMAT的时候其中FORM重复,所以被引用
被引用的两个字节需要计算使用:11101111第一个字节作为偏移位的低位使用,11110001第二个字节的高位4bit作为偏移的高位使用,低四位作为长度使用。
那么组合起来就会变为111111101111为偏移量,而第二个字节0001为长度。

接下来就看图吧,目前自己研究的就是这些,目前还在学习阶段,若有大神愿意指导感激不尽!







niuyufeng 发表于 2025-12-14 09:50

AI 给的

import struct
import os
import zlib

def unpack_wendao_etc(pak_path, output_dir):
    if not os.path.exists(output_dir):
      os.makedirs(output_dir)

    with open(pak_path, 'rb') as f:
      # 1. 读取 Header
      header_data = f.read(16)
      magic, index_size, _, _ = struct.unpack('<4I', header_data)

      if magic != 0x11223344:
            print("错误:文件魔数不匹配,可能不是问道 etc.pak 文件。")
            return

      # 计算文件数量 (每个索引项 64 字节)
      file_count = index_size // 64
      print(f"索引区大小: {index_size}, 文件总数: {file_count}")

      # 2. 读取所有索引数据
      # 索引从 16 字节处开始
      f.seek(16)
      index_data = f.read(index_size)

      for i in range(file_count):
            # 获取当前索引块 (64字节)
            entry = index_data
            
            # 解析结构: Reserved(4), Offset(4), PackedSize(4), UnpackedSize(4), Name(48)
            # <4I 表示 4个 unsigned int (Little Endian)
            reserved, offset, packed_size, unpacked_size = struct.unpack('<4I', entry[:16])
            
            # 提取文件名 (去除尾部 00)
            name_bytes = entry.split(b'\x00')
            try:
                file_name = name_bytes.decode('gbk') # 国产游戏常用 GBK
            except:
                file_name = name_bytes.decode('utf-8', errors='ignore')

            print(f"[{i+1}/{file_count}] 提取: {file_name} (Size: {packed_size} -> {unpacked_size})")

            # 3. 提取文件数据
            # 保存当前索引位置 (虽然我们是一次性读完索引,但为了逻辑清晰)
            current_pos = f.tell()
            
            f.seek(offset)
            file_data = f.read(packed_size)
            
            # 4. 解压处理
            final_data = file_data
            if packed_size != unpacked_size:
                try:
                  # 尝试 Zlib 解压
                  final_data = zlib.decompress(file_data)
                except Exception as e:
                  print(f"-> 解压失败 (Zlib): {e},保存原数据")
                  final_data = file_data

            # 写入磁盘
            out_path = os.path.join(output_dir, file_name)
            # 确保子目录存在 (如果文件名包含路径)
            if "/" in file_name or "\\" in file_name:
                os.makedirs(os.path.dirname(out_path), exist_ok=True)
               
            with open(out_path, 'wb') as out_f:
                out_f.write(final_data)
            
            # 恢复文件指针 (如果不是一次性读取索引的话需要这步)
            f.seek(current_pos)

# 使用示例
# 请将文件路径替换为您实际的路径
# unpack_wendao_etc('etc.pak', 'output_folder')

zhoujianbang 发表于 2025-12-14 15:44

niuyufeng 发表于 2025-12-14 09:50
AI 给的

import struct


这是python吗?我在guthub上看到一个go源码的,我在尝试用C++写一个自己能用习惯的,不过我是刚学,还不会,这个应该用环形缓冲区实现,有时候它里面有文件夹,还得递归

niuyufeng 发表于 2025-12-17 09:21

把你想要的结果告诉AI,等待结果就可以了,C 或 go,AI可以互相改写,时代已经在变了

zhoujianbang 发表于 2025-12-28 15:13

niuyufeng 发表于 2025-12-17 09:21
把你想要的结果告诉AI,等待结果就可以了,C 或 go,AI可以互相改写,时代已经在变了

试过,太乱了,而且我没基础,我没办法修改它的错误的地方,现在在学习了,学成后再写{:1_924:}
页: [1]
查看完整版本: 分享一个PAK文件的内容,觉得很有意思分享一下