home *** CD-ROM | disk | FTP | other *** search
/ Xentax forum attachments archive / xentax.7z / 14707 / Spellforce050818_2.7z / pakfile.py < prev    next >
Encoding:
Python Source  |  2018-08-04  |  5.8 KB  |  179 lines

  1. """
  2.  
  3. A better system for getting resources from PakFiles is needed.
  4.  
  5. At present, we're simply doing pakfiles[1].read_by_name etc., which requires
  6. the magic number 1 for the right file to check for model diffuse maps.  This
  7. fails for models from the expansions which will have their textures in 20 or
  8. 30 something.
  9.  
  10. Although not fastest, it's probably easiest to just search until we find what
  11. is required, aided by a mapping of which types are in each file.  Here is that
  12. mapping:
  13.  
  14. PAK_MAP = {
  15.     'bob': (5, 22, 32'),
  16.     'bor': (4, 22, 32),
  17.     'bsi': (8, 22, 32),
  18.     'dds': (1, 22, 25, 32, 35),
  19.     'des': (6, 22, 25, 32, 35),
  20.     'lua': (10, 21, 34),
  21.     'mp3': (3, 10, 23, 26, 30, 33, 36),
  22.     'msb': (8, 22, 25, 32, 35),
  23.     'msh': (9, 22, 25, 32, 35),
  24.     'sem': (27, ),
  25.     'tga': (0, 22, 25, 32, 35),
  26.     'txt': (0, 22, 23, 26, 32, 33, 35, 36),
  27.     'wav': (2, 20, 23, 30, 33)
  28. }
  29.  
  30. It is probably worth making an enum to name each of the files, for the times
  31. we have to reference one in particular, for example when we grab camera.des
  32. from pakfiles[6] for posing models.  A file list for the original TOoD would
  33. be useful to confirm the naming.  It looks like 21 should be the last pak in
  34. the original game.
  35.  
  36. Example:
  37.  
  38. def fetch_resource(pakfiles, name):
  39.     extension = os.path.splitext(name)[1]
  40.     for pf in PAK_MAP[extension]:
  41.         data = pf.read_by_name(name)
  42.         if data is not None:
  43.             return data
  44.     return None
  45.  
  46. """
  47.  
  48. import io
  49. import os
  50. import struct
  51.  
  52. import utils
  53.  
  54.  
  55. class PakFileHeader:
  56.     RAW_SIZE = 92
  57.     VERSION_STRING = 'MASSIVE PAKFILE V 4.0\r\n\0'
  58.  
  59.     def __init__(self, f):
  60.         raw_data = f.read(PakFileHeader.RAW_SIZE)
  61.         ins = io.BytesIO(raw_data)
  62.         # 4, VERSION_STRING
  63.         self.ver, self.ver_string = struct.unpack('<L24s', ins.read(4 + 24))
  64.         # [0]..[4]: 0, 1245104==12ffb0, 4222728==406f08, 4243768==40c138, -1
  65.         # [5]: 322840 3229b0 322a40 322910 3228e0 322900 3228f0
  66.         # [6]: 4212818==404852
  67.         # [7]: 22, 26, 31, 35; == u9
  68.         # [8]: 4031da
  69.         # [a]: -1
  70.         # [b]: looks like crc32
  71.         self.uvals = struct.unpack('<12L', ins.read(12 * 4))
  72.         self.num_files, self.root_index, self.data_start, self.file_size = \
  73.             struct.unpack('<4L', ins.read(16))
  74.  
  75.  
  76. class FileEntry:
  77.     RAW_SIZE = 16
  78.  
  79.     def __init__(self, ins):
  80.         self.size, self.pos, a, b = struct.unpack('<4L', ins.read(16))
  81.         self.filename_offset = a & 0xffffff
  82.         self.dirname_offset = b & 0xffffff
  83.         # In sf0.pak, there were 2077 entries but only 1932 distinct values;
  84.         # min was 0 max was 2077; is this a sorted order? Were there missing
  85.         # numbers, or shared numbers?
  86.         self.node_num = (a >> 16) | (b >> 24)
  87.  
  88.  
  89. def load_file_entries(f, num_files):
  90.     block_size = FileEntry.RAW_SIZE * num_files
  91.     block = f.read(block_size)
  92.     ins = io.BytesIO(block)
  93.     return [FileEntry(ins) for _ in range(num_files)]
  94.  
  95.  
  96. def process_raw_names(block, file_entries):
  97.     names = {}
  98.     ins = io.BytesIO(block)
  99.     for entry in file_entries:
  100.         if entry.dirname_offset not in names:
  101.             ins.seek(entry.dirname_offset)
  102.             names[entry.dirname_offset] = utils.read_cstr(ins)[::-1]
  103.         # Skip unknown int16 in front of filenames (hash?)
  104.         ins.seek(entry.filename_offset + 2)
  105.         names[entry.filename_offset] = utils.read_cstr(ins)[::-1]
  106.     return names
  107.  
  108.  
  109. class PakFile:
  110.     def __init__(self, filename):
  111.         self.f = open(filename, 'rb')
  112.         self.header = PakFileHeader(self.f)
  113.         self.entries = load_file_entries(self.f, self.header.num_files)
  114.         name_block_size = self.header.data_start - self.f.tell()
  115.         name_block = self.f.read(name_block_size)
  116.         self.names = process_raw_names(name_block, self.entries)
  117.  
  118.     def find_file(self, sought_filename):
  119.         dirname, filename = os.path.split(sought_filename)
  120.         for entry in self.entries:
  121.             if self.names[entry.dirname_offset] != dirname:
  122.                 continue
  123.             if self.names[entry.filename_offset] == filename:
  124.                 return entry
  125.         return None
  126.  
  127.     def dirname_of_entry(self, entry):
  128.         return self.names[entry.dirname_offset]
  129.  
  130.     def dirname_of_offset(self, offset):
  131.         return self.names[offset]
  132.  
  133.     def filename_of_entry(self, entry):
  134.         return self.names[entry.filename_offset]
  135.  
  136.     def filename_of_index(self, index):
  137.         return self.names[self.entries[index].filename_offset]
  138.  
  139.     def read_by_index(self, index):
  140.         entry = self.entries[index]
  141.         self.f.seek(self.header.data_start + entry.pos)
  142.         data = self.f.read(entry.size)
  143.         return data
  144.  
  145.     def read_by_name(self, name):
  146.         entry = self.find_file(name)
  147.         if entry is None:
  148.             return None
  149.         index = self.entries.index(entry)
  150.         return self.read_by_index(index)
  151.  
  152.     def sorted_dir_offsets(self):
  153.         offsets = set(entry.dirname_offset for entry in self.entries)
  154.         return sorted(offsets, key=lambda offset: self.names[offset])
  155.  
  156.     def sorted_file_indices_of_dir_offset(self, offset):
  157.         indices = [i for i in range(len(self.entries))
  158.                    if self.entries[i].dirname_offset == offset]
  159.         return sorted(indices, key=self.filename_of_index)
  160.  
  161.     def get_name_and_size(self, index):
  162.         return self.filename_of_index(index), self.entries[index].size
  163.  
  164.  
  165. class PakFileCollection:
  166.     NUMS = (
  167.         0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 20, 21,
  168.         22, 23, 25, 26, 27, 30, 32, 33, 34, 35, 36
  169.     )
  170.  
  171.     def __init__(self, path):
  172.         self.files = {
  173.             num: PakFile(path + 'sf{}.pak'.format(num))
  174.             for num in PakFileCollection.NUMS
  175.         }
  176.  
  177.     def __getitem__(self, key):
  178.         return self.files[key]
  179.