fapassets.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import os
  2. import hashlib
  3. import struct
  4. from typing import TypedDict
  5. class File(TypedDict):
  6. path: str
  7. size: int
  8. content_path: str
  9. class Dir(TypedDict):
  10. path: str
  11. class FileBundler:
  12. """
  13. u32 magic
  14. u32 version
  15. u32 dirs_count
  16. u32 files_count
  17. u32 signature_size
  18. u8[] signature
  19. Dirs:
  20. u32 dir_name length
  21. u8[] dir_name
  22. Files:
  23. u32 file_name length
  24. u8[] file_name
  25. u32 file_content_size
  26. u8[] file_content
  27. """
  28. def __init__(self, directory_path: str):
  29. self.directory_path = directory_path
  30. self.file_list: list[File] = []
  31. self.directory_list: list[Dir] = []
  32. self._gather()
  33. def _gather(self):
  34. for root, dirs, files in os.walk(self.directory_path):
  35. for file_info in files:
  36. file_path = os.path.join(root, file_info)
  37. file_size = os.path.getsize(file_path)
  38. self.file_list.append(
  39. {
  40. "path": os.path.relpath(file_path, self.directory_path),
  41. "size": file_size,
  42. "content_path": file_path,
  43. }
  44. )
  45. for dir_info in dirs:
  46. dir_path = os.path.join(root, dir_info)
  47. # dir_size = sum(
  48. # os.path.getsize(os.path.join(dir_path, f)) for f in os.listdir(dir_path)
  49. # )
  50. self.directory_list.append(
  51. {
  52. "path": os.path.relpath(dir_path, self.directory_path),
  53. }
  54. )
  55. self.file_list.sort(key=lambda f: f["path"])
  56. self.directory_list.sort(key=lambda d: d["path"])
  57. def export(self, target_path: str):
  58. self._md5_hash = hashlib.md5()
  59. with open(target_path, "wb") as f:
  60. # Write header magic and version
  61. f.write(struct.pack("<II", 0x4F4C5A44, 0x01))
  62. # Write dirs count
  63. f.write(struct.pack("<I", len(self.directory_list)))
  64. # Write files count
  65. f.write(struct.pack("<I", len(self.file_list)))
  66. md5_hash_size = len(self._md5_hash.digest())
  67. # write signature size and null signature, we'll fill it in later
  68. f.write(struct.pack("<I", md5_hash_size))
  69. signature_offset = f.tell()
  70. f.write(b"\x00" * md5_hash_size)
  71. self._write_contents(f)
  72. f.seek(signature_offset)
  73. f.write(self._md5_hash.digest())
  74. def _write_contents(self, f):
  75. for dir_info in self.directory_list:
  76. f.write(struct.pack("<I", len(dir_info["path"]) + 1))
  77. f.write(dir_info["path"].encode("ascii") + b"\x00")
  78. self._md5_hash.update(dir_info["path"].encode("ascii") + b"\x00")
  79. # Write files
  80. for file_info in self.file_list:
  81. f.write(struct.pack("<I", len(file_info["path"]) + 1))
  82. f.write(file_info["path"].encode("ascii") + b"\x00")
  83. f.write(struct.pack("<I", file_info["size"]))
  84. self._md5_hash.update(file_info["path"].encode("ascii") + b"\x00")
  85. with open(file_info["content_path"], "rb") as content_file:
  86. content = content_file.read()
  87. f.write(content)
  88. self._md5_hash.update(content)