fivefury 0.2.8__tar.gz → 0.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. {fivefury-0.2.8 → fivefury-0.2.9}/PKG-INFO +17 -1
  2. fivefury-0.2.8/fivefury.egg-info/PKG-INFO → fivefury-0.2.9/README.md +16 -13
  3. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/__init__.py +26 -0
  4. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/model.py +5 -5
  5. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/core.py +1 -0
  6. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/io.py +3 -0
  7. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/scan.py +1 -1
  8. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/model.py +1 -1
  9. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/gamefile.py +4 -1
  10. fivefury-0.2.9/fivefury/heightmap/__init__.py +31 -0
  11. fivefury-0.2.9/fivefury/heightmap/io.py +373 -0
  12. fivefury-0.2.9/fivefury/heightmap/model.py +535 -0
  13. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/model.py +4 -4
  14. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/builder.py +2 -1
  15. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/model.py +1 -1
  16. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/prepare.py +91 -2
  17. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/writer.py +2 -1
  18. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/entities.py +2 -2
  19. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/__init__.py +1 -1
  20. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/mlo.py +2 -2
  21. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/timed_archetype.py +2 -2
  22. fivefury-0.2.8/README.md → fivefury-0.2.9/fivefury.egg-info/PKG-INFO +29 -0
  23. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/SOURCES.txt +5 -0
  24. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_bindings.h +4 -0
  25. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_crypto.cpp +14 -10
  26. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_index.cpp +14 -8
  27. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_archive.cpp +5 -1
  28. {fivefury-0.2.8 → fivefury-0.2.9}/pyproject.toml +5 -5
  29. {fivefury-0.2.8 → fivefury-0.2.9}/setup.py +4 -1
  30. fivefury-0.2.9/tests/test_heightmap.py +160 -0
  31. fivefury-0.2.9/tests/test_python_compat.py +57 -0
  32. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydr_builder.py +20 -0
  33. {fivefury-0.2.8 → fivefury-0.2.9}/LICENSE +0 -0
  34. {fivefury-0.2.8 → fivefury-0.2.9}/MANIFEST.in +0 -0
  35. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/_native.py +0 -0
  36. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/__init__.py +0 -0
  37. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/base.py +0 -0
  38. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/ydd.py +0 -0
  39. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/ydr.py +0 -0
  40. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/yft.py +0 -0
  41. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/ypt.py +0 -0
  42. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/__init__.py +0 -0
  43. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/audio.py +0 -0
  44. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/constants.py +0 -0
  45. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/conversion.py +0 -0
  46. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/crypto.py +0 -0
  47. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/io.py +0 -0
  48. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/structures.py +0 -0
  49. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/binary.py +0 -0
  50. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/__init__.py +0 -0
  51. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/geometry.py +0 -0
  52. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/materials.py +0 -0
  53. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/reader.py +0 -0
  54. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/writer.py +0 -0
  55. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/buckets.py +0 -0
  56. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/__init__.py +0 -0
  57. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/assets.py +0 -0
  58. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/kinds.py +0 -0
  59. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/paths.py +0 -0
  60. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/views.py +0 -0
  61. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/__init__.py +0 -0
  62. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/reader.py +0 -0
  63. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/resource.py +0 -0
  64. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/shaders.py +0 -0
  65. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/vertices.py +0 -0
  66. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/colors.py +0 -0
  67. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/common.py +0 -0
  68. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/crypto/__init__.py +0 -0
  69. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/crypto/backends.py +0 -0
  70. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/crypto/keys.py +0 -0
  71. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/__init__.py +0 -0
  72. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/analysis.py +0 -0
  73. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/dsl.py +0 -0
  74. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/events.py +0 -0
  75. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/flags.py +0 -0
  76. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/lights.py +0 -0
  77. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/model.py +0 -0
  78. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/names.py +0 -0
  79. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/payloads.py +0 -0
  80. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/pso.py +0 -0
  81. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/__init__.py +0 -0
  82. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/base.py +0 -0
  83. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/bindings.py +0 -0
  84. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/core.py +0 -0
  85. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/event_api.py +0 -0
  86. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/io.py +0 -0
  87. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/shared.py +0 -0
  88. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/subtitles.py +0 -0
  89. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/timeline.py +0 -0
  90. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/validation.py +0 -0
  91. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/schema.py +0 -0
  92. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/write.py +0 -0
  93. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/xml.py +0 -0
  94. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/data/lut.dat +0 -0
  95. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/data/magic.dat +0 -0
  96. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/data/ng.dat +0 -0
  97. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/__init__.py +0 -0
  98. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/enums.py +0 -0
  99. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/highlevel.py +0 -0
  100. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/model.py +0 -0
  101. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/xml.py +0 -0
  102. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/Shaders.xml +0 -0
  103. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/__init__.py +0 -0
  104. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/lod.py +0 -0
  105. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/model.py +0 -0
  106. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/shaders.py +0 -0
  107. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/game_target.py +0 -0
  108. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/gtxd.py +0 -0
  109. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/gxt2.py +0 -0
  110. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/hashing.py +0 -0
  111. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/__init__.py +0 -0
  112. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/backed.py +0 -0
  113. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/builder.py +0 -0
  114. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/defs.py +0 -0
  115. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/read.py +0 -0
  116. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/resource.py +0 -0
  117. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/utils.py +0 -0
  118. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/metahash.py +0 -0
  119. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/__init__.py +0 -0
  120. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/codec.py +0 -0
  121. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/model.py +0 -0
  122. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/reader.py +0 -0
  123. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/schema.py +0 -0
  124. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/writer.py +0 -0
  125. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rbf.py +0 -0
  126. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/__init__.py +0 -0
  127. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/enums.py +0 -0
  128. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/io.py +0 -0
  129. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/resolver.py +0 -0
  130. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/resource.py +0 -0
  131. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/resource_textures.py +0 -0
  132. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/__init__.py +0 -0
  133. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/convert.py +0 -0
  134. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/entries.py +0 -0
  135. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/modes.py +0 -0
  136. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/ps3.py +0 -0
  137. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/streaming.py +0 -0
  138. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/utils.py +0 -0
  139. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/texture.py +0 -0
  140. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vector.py +0 -0
  141. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/__init__.py +0 -0
  142. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/carcols.py +0 -0
  143. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/common.py +0 -0
  144. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/enums.py +0 -0
  145. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/handling.py +0 -0
  146. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/resource.py +0 -0
  147. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/variations.py +0 -0
  148. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/vehicles.py +0 -0
  149. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/__init__.py +0 -0
  150. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/geometry.py +0 -0
  151. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/io.py +0 -0
  152. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/model.py +0 -0
  153. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/xml.py +0 -0
  154. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ybn/__init__.py +0 -0
  155. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ybn/mlo.py +0 -0
  156. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/__init__.py +0 -0
  157. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/cutscene.py +0 -0
  158. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/model.py +0 -0
  159. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/reader.py +0 -0
  160. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequence_channels.py +0 -0
  161. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequence_codec.py +0 -0
  162. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequence_tracks.py +0 -0
  163. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequences.py +0 -0
  164. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/write.py +0 -0
  165. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/__init__.py +0 -0
  166. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/model.py +0 -0
  167. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/reader.py +0 -0
  168. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/rigging.py +0 -0
  169. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/writer.py +0 -0
  170. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/ShadersGen9Conversion.xml +0 -0
  171. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/__init__.py +0 -0
  172. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/assimp.py +0 -0
  173. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/build_types.py +0 -0
  174. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/collision.py +0 -0
  175. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/defs.py +0 -0
  176. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/gen9.py +0 -0
  177. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/gen9_shader_enums.py +0 -0
  178. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/gen9_shader_info.py +0 -0
  179. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/materials.py +0 -0
  180. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_joints.py +0 -0
  181. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_lights.py +0 -0
  182. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_materials.py +0 -0
  183. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_skeleton.py +0 -0
  184. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/reader.py +0 -0
  185. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/rigging.py +0 -0
  186. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/shader_enums.py +0 -0
  187. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/shader_info.py +0 -0
  188. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/shaders.py +0 -0
  189. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_buffers.py +0 -0
  190. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_drawable.py +0 -0
  191. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_geometry.py +0 -0
  192. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_joints.py +0 -0
  193. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_lights.py +0 -0
  194. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_materials.py +0 -0
  195. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_models.py +0 -0
  196. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_skeleton.py +0 -0
  197. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/__init__.py +0 -0
  198. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/audit.py +0 -0
  199. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/constants.py +0 -0
  200. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/enums.py +0 -0
  201. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/instructions.py +0 -0
  202. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/model.py +0 -0
  203. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/ped.py +0 -0
  204. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/reader.py +0 -0
  205. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/writer.py +0 -0
  206. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/__init__.py +0 -0
  207. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/cloth.py +0 -0
  208. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/cloth_reader.py +0 -0
  209. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/cloth_writer.py +0 -0
  210. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/constants.py +0 -0
  211. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/corpus.py +0 -0
  212. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/drawable_reader.py +0 -0
  213. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/drawables.py +0 -0
  214. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/events.py +0 -0
  215. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/events_reader.py +0 -0
  216. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/events_writer.py +0 -0
  217. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/fields_reader.py +0 -0
  218. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/fragment.py +0 -0
  219. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/fragment_drawable.py +0 -0
  220. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/glass.py +0 -0
  221. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/glass_reader.py +0 -0
  222. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/glass_writer.py +0 -0
  223. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/io_helpers.py +0 -0
  224. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/matrices.py +0 -0
  225. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/matrices_reader.py +0 -0
  226. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/matrices_writer.py +0 -0
  227. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/model.py +0 -0
  228. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics.py +0 -0
  229. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics_authoring.py +0 -0
  230. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics_reader.py +0 -0
  231. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics_writer.py +0 -0
  232. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/pointers.py +0 -0
  233. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/reader.py +0 -0
  234. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/stats.py +0 -0
  235. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/validation.py +0 -0
  236. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/__init__.py +0 -0
  237. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/base.py +0 -0
  238. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/blocks.py +0 -0
  239. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/cargens.py +0 -0
  240. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/defs.py +0 -0
  241. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/enums.py +0 -0
  242. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/extension_defs.py +0 -0
  243. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/extensions.py +0 -0
  244. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/grass.py +0 -0
  245. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/io.py +0 -0
  246. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/lights.py +0 -0
  247. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/mlo_validation.py +0 -0
  248. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/model.py +0 -0
  249. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/occluders.py +0 -0
  250. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/packing.py +0 -0
  251. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/surfaces.py +0 -0
  252. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/timecycle.py +0 -0
  253. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/utils.py +0 -0
  254. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/__init__.py +0 -0
  255. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/builder.py +0 -0
  256. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/enums.py +0 -0
  257. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/model.py +0 -0
  258. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/pso.py +0 -0
  259. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/resource.py +0 -0
  260. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/schema.py +0 -0
  261. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/utils.py +0 -0
  262. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/ped_metadata.py +0 -0
  263. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/ped_variation.py +0 -0
  264. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/scenario.py +0 -0
  265. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/streaming.py +0 -0
  266. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/__init__.py +0 -0
  267. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/heightmaps.py +0 -0
  268. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/model.py +0 -0
  269. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/network.py +0 -0
  270. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/reader.py +0 -0
  271. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/regions.py +0 -0
  272. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/writer.py +0 -0
  273. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/__init__.py +0 -0
  274. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/assimp.py +0 -0
  275. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/model.py +0 -0
  276. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/reader.py +0 -0
  277. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/writer.py +0 -0
  278. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytd/__init__.py +0 -0
  279. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytd/defs.py +0 -0
  280. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytd/model.py +0 -0
  281. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/__init__.py +0 -0
  282. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/archetypes.py +0 -0
  283. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/asset_types.py +0 -0
  284. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/base_archetype.py +0 -0
  285. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/defs.py +0 -0
  286. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/extension_defs.py +0 -0
  287. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/extensions.py +0 -0
  288. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/flags.py +0 -0
  289. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/helpers.py +0 -0
  290. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/lod.py +0 -0
  291. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/mlo_validation.py +0 -0
  292. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/model.py +0 -0
  293. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/dependency_links.txt +0 -0
  294. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/requires.txt +0 -0
  295. {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/top_level.txt +0 -0
  296. {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_algorithms.cpp +0 -0
  297. {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_algorithms.h +0 -0
  298. {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_python.cpp +0 -0
  299. {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_python.h +0 -0
  300. {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_types.h +0 -0
  301. {fivefury-0.2.8 → fivefury-0.2.9}/native/crypto_magic.cpp +0 -0
  302. {fivefury-0.2.8 → fivefury-0.2.9}/native/crypto_magic.h +0 -0
  303. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_bindings.cpp +0 -0
  304. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_bounds.cpp +0 -0
  305. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_module.cpp +0 -0
  306. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_resource.cpp +0 -0
  307. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_rpf.cpp +0 -0
  308. {fivefury-0.2.8 → fivefury-0.2.9}/native/py_vertex.cpp +0 -0
  309. {fivefury-0.2.8 → fivefury-0.2.9}/native/resource_layout.cpp +0 -0
  310. {fivefury-0.2.8 → fivefury-0.2.9}/native/resource_layout.h +0 -0
  311. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_archive.h +0 -0
  312. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_crypto.cpp +0 -0
  313. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_index.cpp +0 -0
  314. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_index.h +0 -0
  315. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_read.cpp +0 -0
  316. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_scan.cpp +0 -0
  317. {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_scan.h +0 -0
  318. {fivefury-0.2.8 → fivefury-0.2.9}/setup.cfg +0 -0
  319. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cdr.py +0 -0
  320. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cut.py +0 -0
  321. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cut_dsl.py +0 -0
  322. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cut_no_template.py +0 -0
  323. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_dlc.py +0 -0
  324. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_drawable.py +0 -0
  325. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_gtxd.py +0 -0
  326. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_hashing.py +0 -0
  327. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_meta_rpf_contracts.py +0 -0
  328. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_mlo.py +0 -0
  329. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_rpf_ps3.py +0 -0
  330. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_rpf_streaming_folder.py +0 -0
  331. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_vehiclemeta.py +0 -0
  332. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_water.py +0 -0
  333. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ybn.py +0 -0
  334. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ycd.py +0 -0
  335. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ycd_cutscene_builder.py +0 -0
  336. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydd.py +0 -0
  337. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydr.py +0 -0
  338. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydr_shaders.py +0 -0
  339. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_yft.py +0 -0
  340. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ymf.py +0 -0
  341. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ynd.py +0 -0
  342. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ynv.py +0 -0
  343. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ynv_assimp.py +0 -0
  344. {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ytyp_flags.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fivefury
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: Python library for GTA V asset workflows including YMAP, YTYP, RPF, YTD and GameFileCache utilities.
5
5
  Author: Codex
6
6
  License-Expression: CC0-1.0
@@ -42,6 +42,7 @@ The Assimp-backed import helpers (`assimp_to_ydr`, `obj_to_ydr`, `fbx_to_ydr`, `
42
42
  | `YTD` | Full | Texture dictionaries: read/write, extraction, embedded-asset helpers |
43
43
  | `YND` | Full | Path nodes, links, area partitioning, junction heightmaps |
44
44
  | `YNV` | Full | Navmeshes: sectors, polys, portals, validation, basic OBJ partitioning |
45
+ | `heightmap.dat` | Full | World-height grids, native quantization, row RLE, water masks, spatial queries |
45
46
  | `water.xml` | Full | Water surfaces, triangle corners, calming regions, wave amplitude and direction |
46
47
  | `CUT` | Full | Cutscenes, plus the readable `.cuts` script format for round-trip authoring |
47
48
  | `GXT2` | Full | Hashed text tables with binary read/write and text import/export |
@@ -140,6 +141,21 @@ water.add(
140
141
  water.translate(x=500, y=-200, z=20.0).save("water.xml")
141
142
  ```
142
143
 
144
+ ### Author a world height map
145
+
146
+ ```python
147
+ from fivefury import HeightMap, HeightMapBounds
148
+
149
+ heightmap = HeightMap.empty(
150
+ columns=10,
151
+ rows=10,
152
+ bounds=HeightMapBounds(0.0, 0.0, 0.0, 500.0, 500.0, 800.0),
153
+ )
154
+ heightmap.set_height(4, 6, minimum=32.0, maximum=78.0)
155
+ heightmap.set_water(4, 6)
156
+ heightmap.save("heightmap.dat")
157
+ ```
158
+
143
159
  ### Convert audio to AWC
144
160
 
145
161
  ```python
@@ -1,16 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: fivefury
3
- Version: 0.2.8
4
- Summary: Python library for GTA V asset workflows including YMAP, YTYP, RPF, YTD and GameFileCache utilities.
5
- Author: Codex
6
- License-Expression: CC0-1.0
7
- Requires-Python: >=3.11
8
- Description-Content-Type: text/markdown
9
- License-File: LICENSE
10
- Requires-Dist: miniaudio>=1.71
11
- Requires-Dist: numpy>=1.26
12
- Dynamic: license-file
13
-
14
1
  # FiveFury
15
2
 
16
3
  FiveFury is a Python library for reading, writing, and packaging GTA V asset files: drawables, collisions, map metadata, animations, navigation data, texture dictionaries, text tables, audio containers, cutscenes, DLC metadata, and RPF archives.
@@ -42,6 +29,7 @@ The Assimp-backed import helpers (`assimp_to_ydr`, `obj_to_ydr`, `fbx_to_ydr`, `
42
29
  | `YTD` | Full | Texture dictionaries: read/write, extraction, embedded-asset helpers |
43
30
  | `YND` | Full | Path nodes, links, area partitioning, junction heightmaps |
44
31
  | `YNV` | Full | Navmeshes: sectors, polys, portals, validation, basic OBJ partitioning |
32
+ | `heightmap.dat` | Full | World-height grids, native quantization, row RLE, water masks, spatial queries |
45
33
  | `water.xml` | Full | Water surfaces, triangle corners, calming regions, wave amplitude and direction |
46
34
  | `CUT` | Full | Cutscenes, plus the readable `.cuts` script format for round-trip authoring |
47
35
  | `GXT2` | Full | Hashed text tables with binary read/write and text import/export |
@@ -140,6 +128,21 @@ water.add(
140
128
  water.translate(x=500, y=-200, z=20.0).save("water.xml")
141
129
  ```
142
130
 
131
+ ### Author a world height map
132
+
133
+ ```python
134
+ from fivefury import HeightMap, HeightMapBounds
135
+
136
+ heightmap = HeightMap.empty(
137
+ columns=10,
138
+ rows=10,
139
+ bounds=HeightMapBounds(0.0, 0.0, 0.0, 500.0, 500.0, 800.0),
140
+ )
141
+ heightmap.set_height(4, 6, minimum=32.0, maximum=78.0)
142
+ heightmap.set_water(4, 6)
143
+ heightmap.save("heightmap.dat")
144
+ ```
145
+
143
146
  ### Convert audio to AWC
144
147
 
145
148
  ```python
@@ -459,6 +459,20 @@ from .rpf import (
459
459
  zip_to_rpf,
460
460
  )
461
461
  from .texture import BCFormat
462
+ from .heightmap import (
463
+ HeightCell,
464
+ HeightGrid,
465
+ HeightMap,
466
+ HeightMapBounds,
467
+ HeightMapByteOrder,
468
+ HeightMapCellFormat,
469
+ HeightMapFlags,
470
+ HeightMapValidationError,
471
+ build_heightmap_bytes,
472
+ create_heightmap,
473
+ read_heightmap,
474
+ save_heightmap,
475
+ )
462
476
  from .water import (
463
477
  WaterAlpha,
464
478
  WaterBounds,
@@ -1218,6 +1232,14 @@ __all__ = [
1218
1232
  "GameTarget",
1219
1233
  "GameCrypto",
1220
1234
  "GameFileType",
1235
+ "HeightCell",
1236
+ "HeightGrid",
1237
+ "HeightMap",
1238
+ "HeightMapBounds",
1239
+ "HeightMapByteOrder",
1240
+ "HeightMapCellFormat",
1241
+ "HeightMapFlags",
1242
+ "HeightMapValidationError",
1221
1243
  "GXT2_MAGIC",
1222
1244
  "GXT2_MAGIC_BYTES",
1223
1245
  "Gxt2",
@@ -1594,6 +1616,7 @@ __all__ = [
1594
1616
  "infer_archetype_radius",
1595
1617
  "mark_cutscene_prop_archetypes",
1596
1618
  "assimp_to_ydr",
1619
+ "build_heightmap_bytes",
1597
1620
  "build_rsc7",
1598
1621
  "build_water_xml",
1599
1622
  "build_ynv_bytes",
@@ -1617,6 +1640,7 @@ __all__ = [
1617
1640
  "clear_hash_resolver",
1618
1641
  "clear_game_crypto",
1619
1642
  "create_rpf",
1643
+ "create_heightmap",
1620
1644
  "create_water",
1621
1645
  "create_ymf_for_ymaps",
1622
1646
  "create_ydd",
@@ -1701,6 +1725,7 @@ __all__ = [
1701
1725
  "read_ymap",
1702
1726
  "read_vehicle_meta",
1703
1727
  "read_water",
1728
+ "read_heightmap",
1704
1729
  "read_ymf",
1705
1730
  "read_ymf_xml",
1706
1731
  "read_ymt",
@@ -1731,6 +1756,7 @@ __all__ = [
1731
1756
  "save_ytd",
1732
1757
  "save_ytyp",
1733
1758
  "save_water",
1759
+ "save_heightmap",
1734
1760
  "set_ped_drawable_cloth",
1735
1761
  "time_flags",
1736
1762
  "save_cut",
@@ -1054,7 +1054,7 @@ class BoundGeometry(Bound):
1054
1054
  return material
1055
1055
 
1056
1056
  def build(self) -> "BoundGeometry":
1057
- super().build()
1057
+ Bound.build(self)
1058
1058
  for index, polygon in enumerate(self.polygons):
1059
1059
  polygon.index = index
1060
1060
  if isinstance(polygon, BoundPolygonTriangle):
@@ -1079,7 +1079,7 @@ class BoundGeometry(Bound):
1079
1079
  return self
1080
1080
 
1081
1081
  def validate(self) -> list[str]:
1082
- issues = super().validate()
1082
+ issues = Bound.validate(self)
1083
1083
  if len(self.polygon_material_indices) != len(self.polygons):
1084
1084
  issues.append("polygon_material_indices length does not match polygon count")
1085
1085
  for polygon_index, polygon in enumerate(self.polygons):
@@ -1164,7 +1164,7 @@ class BoundComposite(Bound):
1164
1164
  def compute_volume(self) -> float:
1165
1165
  if self.children:
1166
1166
  return sum(child.bound.compute_volume() for child in self.children)
1167
- return super().compute_volume()
1167
+ return Bound.compute_volume(self)
1168
1168
 
1169
1169
  def compute_center_of_gravity(
1170
1170
  self,
@@ -1226,7 +1226,7 @@ class BoundComposite(Bound):
1226
1226
  return self.compute_composite_angular_inertia(mass)
1227
1227
 
1228
1228
  def build(self) -> "BoundComposite":
1229
- super().build()
1229
+ Bound.build(self)
1230
1230
  for child in self.children:
1231
1231
  child.bound.build()
1232
1232
  if child.bounds is None or not _aabb_is_valid(child.bounds):
@@ -1252,7 +1252,7 @@ class BoundComposite(Bound):
1252
1252
  return self
1253
1253
 
1254
1254
  def validate(self) -> list[str]:
1255
- issues = super().validate()
1255
+ issues = Bound.validate(self)
1256
1256
  if not self.children:
1257
1257
  issues.append("Composite bound has no children")
1258
1258
  for index, child in enumerate(self.children):
@@ -554,6 +554,7 @@ _KIND_DICT_TYPES: dict[str, GameFileType] = {
554
554
  "GtxdDict": GameFileType.GTXD,
555
555
  "AwcDict": GameFileType.AWC,
556
556
  "YedDict": GameFileType.YED,
557
+ "HeightMapDict": GameFileType.HEIGHTMAP,
557
558
  "WaterDict": GameFileType.WATER,
558
559
  "WaterMapDict": GameFileType.WATER,
559
560
  }
@@ -10,6 +10,7 @@ from ..cut import read_cut
10
10
  from ..gamefile import GameFile, GameFileType, guess_game_file_type
11
11
  from ..gtxd import read_gtxd
12
12
  from ..gxt2 import read_gxt2
13
+ from ..heightmap import read_heightmap
13
14
  from ..hashing import _get_lut
14
15
  from ..metahash import MetaHash
15
16
  from ..rel import read_rel
@@ -66,6 +67,8 @@ def _decode_dynamic(data: bytes, *, module_name: str, attribute: str, kind: Game
66
67
  def _decode_payload(path: str, data: bytes, *, raw: bytes | None = None) -> tuple[Any, GameFileType]:
67
68
  ext = Path(path).suffix.lower()
68
69
  name = Path(path).name.lower()
70
+ if name.startswith("heightmap") and name.endswith(".dat"):
71
+ return _decode_or_fallback(GameFileType.HEIGHTMAP, data, data, read_heightmap)
69
72
  if name == "water.xml":
70
73
  return _decode_or_fallback(GameFileType.WATER, data, data, read_water)
71
74
  if name == "gtxd.meta":
@@ -15,7 +15,7 @@ from ..gamefile import GameFileType, guess_game_file_type
15
15
  from ..hashing import _get_lut
16
16
  from ..rpf import RpfArchive, RpfFileEntry, _normalize_key
17
17
 
18
- _SCAN_INDEX_VERSION = 9
18
+ _SCAN_INDEX_VERSION = 10
19
19
  _SCAN_GC_INTERVAL = 8
20
20
 
21
21
  _FLAG_LOOSE = 1
@@ -284,7 +284,7 @@ class Cdr(DrawableAsset[CdrMaterial, CdrModel, CdrMesh]):
284
284
  graphics_data: bytes = dataclasses.field(default=b"", repr=False)
285
285
 
286
286
  def __post_init__(self) -> None:
287
- super().__post_init__()
287
+ DrawableAsset.__post_init__(self)
288
288
  self.render_bucket_masks = {
289
289
  DrawableLod(lod): int(mask)
290
290
  for lod, mask in self.render_bucket_masks.items()
@@ -80,6 +80,9 @@ _FILE_TYPE_MAP: dict[str, GameFileType] = {
80
80
 
81
81
  def guess_game_file_type(path: str | Path, default: GameFileType = GameFileType.UNKNOWN) -> GameFileType:
82
82
  parsed = Path(str(path))
83
+ normalized_name = parsed.name.lower()
84
+ if normalized_name.startswith("heightmap") and normalized_name.endswith(".dat"):
85
+ return GameFileType.HEIGHTMAP
83
86
  named_meta_types = {
84
87
  "gtxd.meta": GameFileType.GTXD,
85
88
  "vehicles.meta": GameFileType.VEHICLES,
@@ -91,7 +94,7 @@ def guess_game_file_type(path: str | Path, default: GameFileType = GameFileType.
91
94
  "peds.meta": GameFileType.PEDS,
92
95
  "water.xml": GameFileType.WATER,
93
96
  }
94
- named_type = named_meta_types.get(parsed.name.lower())
97
+ named_type = named_meta_types.get(normalized_name)
95
98
  if named_type is not None:
96
99
  return named_type
97
100
  return _FILE_TYPE_MAP.get(parsed.suffix.lower(), default)
@@ -0,0 +1,31 @@
1
+ from .io import (
2
+ build_heightmap_bytes,
3
+ create_heightmap,
4
+ read_heightmap,
5
+ save_heightmap,
6
+ )
7
+ from .model import (
8
+ HeightCell,
9
+ HeightGrid,
10
+ HeightMap,
11
+ HeightMapBounds,
12
+ HeightMapByteOrder,
13
+ HeightMapCellFormat,
14
+ HeightMapFlags,
15
+ HeightMapValidationError,
16
+ )
17
+
18
+ __all__ = [
19
+ "HeightCell",
20
+ "HeightGrid",
21
+ "HeightMap",
22
+ "HeightMapBounds",
23
+ "HeightMapByteOrder",
24
+ "HeightMapCellFormat",
25
+ "HeightMapFlags",
26
+ "HeightMapValidationError",
27
+ "build_heightmap_bytes",
28
+ "create_heightmap",
29
+ "read_heightmap",
30
+ "save_heightmap",
31
+ ]
@@ -0,0 +1,373 @@
1
+ from __future__ import annotations
2
+
3
+ import struct
4
+ from pathlib import Path
5
+
6
+ from .model import (
7
+ HeightCell,
8
+ HeightGrid,
9
+ HeightMap,
10
+ HeightMapBounds,
11
+ HeightMapByteOrder,
12
+ HeightMapCellFormat,
13
+ HeightMapFlags,
14
+ )
15
+
16
+ _HEADER_SIZE = 44
17
+ _RLE_ROW_SIZE = 8
18
+ _TAG = b"HMAP"
19
+ _REVERSED_TAG = _TAG[::-1]
20
+
21
+
22
+ def _source_bytes(source: bytes | bytearray | memoryview | str | Path) -> bytes:
23
+ if isinstance(source, (bytes, bytearray, memoryview)):
24
+ return bytes(source)
25
+ return Path(source).read_bytes()
26
+
27
+
28
+ def _endian_prefix(order: HeightMapByteOrder) -> str:
29
+ return ">" if order is HeightMapByteOrder.BIG else "<"
30
+
31
+
32
+ def _cell_format(cell_format: HeightMapCellFormat) -> str:
33
+ return {
34
+ HeightMapCellFormat.UINT8: "B",
35
+ HeightMapCellFormat.UINT16: "H",
36
+ HeightMapCellFormat.FLOAT32: "f",
37
+ }[cell_format]
38
+
39
+
40
+ def _unpack_cells(
41
+ data: bytes,
42
+ offset: int,
43
+ count: int,
44
+ *,
45
+ endian: str,
46
+ cell_format: HeightMapCellFormat,
47
+ ) -> tuple[list[HeightCell], int]:
48
+ size = int(cell_format)
49
+ end = offset + count * size
50
+ if end > len(data):
51
+ raise ValueError("height-map cell data is truncated")
52
+ if count == 0:
53
+ return [], end
54
+ values = struct.unpack_from(
55
+ f"{endian}{count}{_cell_format(cell_format)}", data, offset
56
+ )
57
+ return list(values), end
58
+
59
+
60
+ def _pack_cells(
61
+ values: list[HeightCell],
62
+ *,
63
+ endian: str,
64
+ cell_format: HeightMapCellFormat,
65
+ ) -> bytes:
66
+ if not values:
67
+ return b""
68
+ return struct.pack(
69
+ f"{endian}{len(values)}{_cell_format(cell_format)}",
70
+ *values,
71
+ )
72
+
73
+
74
+ def _unpack_water_mask(data: bytes, cell_count: int) -> list[bool]:
75
+ return [bool(data[index // 8] & (1 << (index % 8))) for index in range(cell_count)]
76
+
77
+
78
+ def _pack_water_mask(cells: list[bool]) -> bytes:
79
+ result = bytearray((len(cells) + 7) // 8)
80
+ for index, enabled in enumerate(cells):
81
+ if enabled:
82
+ result[index // 8] |= 1 << (index % 8)
83
+ return bytes(result)
84
+
85
+
86
+ def read_heightmap(
87
+ source: bytes | bytearray | memoryview | str | Path,
88
+ ) -> HeightMap:
89
+ data = _source_bytes(source)
90
+ if len(data) < _HEADER_SIZE:
91
+ raise ValueError("height-map data is shorter than its 44-byte header")
92
+ if data[:4] == _TAG:
93
+ order = HeightMapByteOrder.BIG
94
+ elif data[:4] == _REVERSED_TAG:
95
+ order = HeightMapByteOrder.LITTLE
96
+ else:
97
+ raise ValueError("height-map data does not start with the HMAP tag")
98
+
99
+ endian = _endian_prefix(order)
100
+ (
101
+ _,
102
+ version,
103
+ cell_size,
104
+ raw_flags,
105
+ columns,
106
+ rows,
107
+ min_x,
108
+ min_y,
109
+ min_z,
110
+ max_x,
111
+ max_y,
112
+ max_z,
113
+ data_size,
114
+ ) = struct.unpack_from(f"{endian}4sBB2xIHH6fI", data)
115
+ try:
116
+ cell_format = HeightMapCellFormat(cell_size)
117
+ except ValueError as exc:
118
+ raise ValueError(f"unsupported height-map cell size: {cell_size}") from exc
119
+ if not 1 <= columns <= 1000 or not 1 <= rows <= 1000:
120
+ raise ValueError("height-map dimensions must be between 1 and 1000 cells")
121
+
122
+ flags = HeightMapFlags(raw_flags)
123
+ mask_size = (columns * rows + 7) // 8 if flags & HeightMapFlags.WATER_MASK else 0
124
+ expected_size = _HEADER_SIZE + data_size + mask_size
125
+ if len(data) != expected_size:
126
+ raise ValueError(
127
+ f"height-map size mismatch: header describes {expected_size} bytes, got {len(data)}"
128
+ )
129
+
130
+ payload = data[_HEADER_SIZE : _HEADER_SIZE + data_size]
131
+ cell_count = columns * rows
132
+ empty: HeightCell = 0.0 if cell_format is HeightMapCellFormat.FLOAT32 else 0
133
+ minimum_cells = [empty] * cell_count
134
+ maximum_cells = [empty] * cell_count
135
+
136
+ if flags & HeightMapFlags.RLE_DATA:
137
+ table_size = rows * _RLE_ROW_SIZE
138
+ if len(payload) < table_size:
139
+ raise ValueError("height-map RLE table is truncated")
140
+ compact_bytes = len(payload) - table_size
141
+ pair_size = int(cell_format) * 2
142
+ if compact_bytes % pair_size:
143
+ raise ValueError(
144
+ "height-map RLE payload is not aligned to min/max cell pairs"
145
+ )
146
+ compact_count = compact_bytes // pair_size
147
+ rle_rows = [
148
+ struct.unpack_from(f"{endian}HHi", payload, row * _RLE_ROW_SIZE)
149
+ for row in range(rows)
150
+ ]
151
+ offset = table_size
152
+ maximum_compact, offset = _unpack_cells(
153
+ payload,
154
+ offset,
155
+ compact_count,
156
+ endian=endian,
157
+ cell_format=cell_format,
158
+ )
159
+ minimum_compact, offset = _unpack_cells(
160
+ payload,
161
+ offset,
162
+ compact_count,
163
+ endian=endian,
164
+ cell_format=cell_format,
165
+ )
166
+ if offset != len(payload):
167
+ raise ValueError("height-map RLE payload contains trailing bytes")
168
+
169
+ for row, (start, count, data_offset) in enumerate(rle_rows):
170
+ if start > columns or count > columns - start:
171
+ raise ValueError(f"height-map RLE row {row} exceeds the column count")
172
+ compact_start = data_offset + start
173
+ if compact_start < 0 or compact_start + count > compact_count:
174
+ raise ValueError(
175
+ f"height-map RLE row {row} points outside the compact data"
176
+ )
177
+ dense_start = row * columns + start
178
+ dense_stop = dense_start + count
179
+ minimum_cells[dense_start:dense_stop] = minimum_compact[
180
+ compact_start : compact_start + count
181
+ ]
182
+ maximum_cells[dense_start:dense_stop] = maximum_compact[
183
+ compact_start : compact_start + count
184
+ ]
185
+ else:
186
+ expected_data_size = cell_count * int(cell_format) * 2
187
+ if data_size != expected_data_size:
188
+ raise ValueError(
189
+ f"height-map cell payload should contain {expected_data_size} bytes, got {data_size}"
190
+ )
191
+ maximum_cells, offset = _unpack_cells(
192
+ payload,
193
+ 0,
194
+ cell_count,
195
+ endian=endian,
196
+ cell_format=cell_format,
197
+ )
198
+ minimum_cells, offset = _unpack_cells(
199
+ payload,
200
+ offset,
201
+ cell_count,
202
+ endian=endian,
203
+ cell_format=cell_format,
204
+ )
205
+ if offset != len(payload):
206
+ raise ValueError("height-map cell payload contains trailing bytes")
207
+
208
+ water_cells = None
209
+ if mask_size:
210
+ water_cells = _unpack_water_mask(data[-mask_size:], cell_count)
211
+
212
+ result = HeightMap(
213
+ columns=columns,
214
+ rows=rows,
215
+ bounds=HeightMapBounds(min_x, min_y, min_z, max_x, max_y, max_z),
216
+ minimum_cells=minimum_cells,
217
+ maximum_cells=maximum_cells,
218
+ water_cells=water_cells,
219
+ cell_format=cell_format,
220
+ version=version,
221
+ flags=flags,
222
+ source_byte_order=order,
223
+ )
224
+ result.ensure_valid(game_compatible=False)
225
+ return result
226
+
227
+
228
+ def _compact_rows(
229
+ heightmap: HeightMap,
230
+ ) -> tuple[list[tuple[int, int, int]], list[HeightCell], list[HeightCell]]:
231
+ rows: list[tuple[int, int, int]] = []
232
+ maximum: list[HeightCell] = []
233
+ minimum: list[HeightCell] = []
234
+ compact_count = 0
235
+ ranges: list[tuple[int, int]] = []
236
+
237
+ for row in range(heightmap.rows):
238
+ row_offset = row * heightmap.columns
239
+ start = 0
240
+ stop = heightmap.columns - 1
241
+ while start < heightmap.columns:
242
+ index = row_offset + start
243
+ if (
244
+ heightmap.minimum_cells[index] != 0
245
+ or heightmap.maximum_cells[index] != 0
246
+ ):
247
+ break
248
+ start += 1
249
+ while stop >= start:
250
+ index = row_offset + stop
251
+ if (
252
+ heightmap.minimum_cells[index] != 0
253
+ or heightmap.maximum_cells[index] != 0
254
+ ):
255
+ break
256
+ stop -= 1
257
+ count = max(0, stop - start + 1)
258
+ rows.append(
259
+ (start if count else 0, count, compact_count - start if count else 0)
260
+ )
261
+ ranges.append((row_offset + start, count))
262
+ compact_count += count
263
+
264
+ for start, count in ranges:
265
+ maximum.extend(heightmap.maximum_cells[start : start + count])
266
+ for start, count in ranges:
267
+ minimum.extend(heightmap.minimum_cells[start : start + count])
268
+ return rows, maximum, minimum
269
+
270
+
271
+ def build_heightmap_bytes(
272
+ source: HeightMap,
273
+ *,
274
+ rle: bool | None = None,
275
+ byte_order: HeightMapByteOrder = HeightMapByteOrder.BIG,
276
+ game_compatible: bool = True,
277
+ ) -> bytes:
278
+ heightmap = source.build().ensure_valid(game_compatible=game_compatible)
279
+ order = HeightMapByteOrder(byte_order)
280
+ endian = _endian_prefix(order)
281
+ use_rle = heightmap.uses_rle if rle is None else bool(rle)
282
+
283
+ if use_rle:
284
+ rows, maximum, minimum = _compact_rows(heightmap)
285
+ table = b"".join(struct.pack(f"{endian}HHi", *row) for row in rows)
286
+ payload = (
287
+ table
288
+ + _pack_cells(maximum, endian=endian, cell_format=heightmap.cell_format)
289
+ + _pack_cells(minimum, endian=endian, cell_format=heightmap.cell_format)
290
+ )
291
+ else:
292
+ payload = _pack_cells(
293
+ heightmap.maximum_cells,
294
+ endian=endian,
295
+ cell_format=heightmap.cell_format,
296
+ ) + _pack_cells(
297
+ heightmap.minimum_cells,
298
+ endian=endian,
299
+ cell_format=heightmap.cell_format,
300
+ )
301
+
302
+ flags = int(heightmap.flags)
303
+ if use_rle:
304
+ flags |= int(HeightMapFlags.RLE_DATA)
305
+ else:
306
+ flags &= ~int(HeightMapFlags.RLE_DATA)
307
+ if heightmap.water_cells is not None:
308
+ flags |= int(HeightMapFlags.WATER_MASK)
309
+ water_mask = _pack_water_mask(heightmap.water_cells)
310
+ else:
311
+ flags &= ~int(HeightMapFlags.WATER_MASK)
312
+ water_mask = b""
313
+
314
+ tag = _TAG if order is HeightMapByteOrder.BIG else _REVERSED_TAG
315
+ bounds = heightmap.bounds
316
+ header = struct.pack(
317
+ f"{endian}4sBB2xIHH6fI",
318
+ tag,
319
+ heightmap.version,
320
+ int(heightmap.cell_format),
321
+ flags,
322
+ heightmap.columns,
323
+ heightmap.rows,
324
+ bounds.min_x,
325
+ bounds.min_y,
326
+ bounds.min_z,
327
+ bounds.max_x,
328
+ bounds.max_y,
329
+ bounds.max_z,
330
+ len(payload),
331
+ )
332
+ return header + payload + water_mask
333
+
334
+
335
+ def create_heightmap(
336
+ minimum: HeightGrid,
337
+ maximum: HeightGrid | None = None,
338
+ *,
339
+ bounds: HeightMapBounds | tuple[float, float, float, float, float, float],
340
+ water: list[list[bool]] | None = None,
341
+ cell_format: HeightMapCellFormat = HeightMapCellFormat.UINT8,
342
+ ) -> HeightMap:
343
+ return HeightMap.from_height_grids(
344
+ minimum,
345
+ maximum,
346
+ bounds=bounds,
347
+ water=water,
348
+ cell_format=cell_format,
349
+ )
350
+
351
+
352
+ def save_heightmap(
353
+ source: HeightMap,
354
+ destination: str | Path,
355
+ *,
356
+ rle: bool | None = None,
357
+ byte_order: HeightMapByteOrder = HeightMapByteOrder.BIG,
358
+ game_compatible: bool = True,
359
+ ) -> Path:
360
+ return source.save(
361
+ destination,
362
+ rle=rle,
363
+ byte_order=byte_order,
364
+ game_compatible=game_compatible,
365
+ )
366
+
367
+
368
+ __all__ = [
369
+ "build_heightmap_bytes",
370
+ "create_heightmap",
371
+ "read_heightmap",
372
+ "save_heightmap",
373
+ ]