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.
- {fivefury-0.2.8 → fivefury-0.2.9}/PKG-INFO +17 -1
- fivefury-0.2.8/fivefury.egg-info/PKG-INFO → fivefury-0.2.9/README.md +16 -13
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/__init__.py +26 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/model.py +5 -5
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/core.py +1 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/io.py +3 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/scan.py +1 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/model.py +1 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/gamefile.py +4 -1
- fivefury-0.2.9/fivefury/heightmap/__init__.py +31 -0
- fivefury-0.2.9/fivefury/heightmap/io.py +373 -0
- fivefury-0.2.9/fivefury/heightmap/model.py +535 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/model.py +4 -4
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/builder.py +2 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/model.py +1 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/prepare.py +91 -2
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/writer.py +2 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/entities.py +2 -2
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/__init__.py +1 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/mlo.py +2 -2
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/timed_archetype.py +2 -2
- fivefury-0.2.8/README.md → fivefury-0.2.9/fivefury.egg-info/PKG-INFO +29 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/SOURCES.txt +5 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_bindings.h +4 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_crypto.cpp +14 -10
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_index.cpp +14 -8
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_archive.cpp +5 -1
- {fivefury-0.2.8 → fivefury-0.2.9}/pyproject.toml +5 -5
- {fivefury-0.2.8 → fivefury-0.2.9}/setup.py +4 -1
- fivefury-0.2.9/tests/test_heightmap.py +160 -0
- fivefury-0.2.9/tests/test_python_compat.py +57 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydr_builder.py +20 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/LICENSE +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/MANIFEST.in +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/_native.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/base.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/ydd.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/ydr.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/yft.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/assets/ypt.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/audio.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/constants.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/conversion.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/crypto.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/io.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/awc/structures.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/binary.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/geometry.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/materials.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/bounds/writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/buckets.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/assets.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/kinds.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/paths.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cache/views.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/resource.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/shaders.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cdr/vertices.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/colors.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/common.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/crypto/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/crypto/backends.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/crypto/keys.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/analysis.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/dsl.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/events.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/flags.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/lights.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/names.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/payloads.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/pso.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/base.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/bindings.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/core.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/event_api.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/io.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/shared.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/subtitles.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/timeline.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/scene/validation.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/schema.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/write.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/cut/xml.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/data/lut.dat +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/data/magic.dat +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/data/ng.dat +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/highlevel.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/dlc/xml.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/Shaders.xml +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/lod.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/drawable/shaders.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/game_target.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/gtxd.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/gxt2.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/hashing.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/backed.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/builder.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/read.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/resource.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/meta/utils.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/metahash.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/codec.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/schema.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/pso/writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rbf.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rel/io.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/resolver.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/resource.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/resource_textures.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/convert.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/entries.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/modes.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/ps3.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/streaming.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/rpf/utils.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/texture.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vector.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/carcols.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/common.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/handling.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/resource.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/variations.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/vehiclemeta/vehicles.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/geometry.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/io.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/water/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/xml.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ybn/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ybn/mlo.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/cutscene.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequence_channels.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequence_codec.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequence_tracks.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/sequences.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ycd/write.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/rigging.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydd/writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/ShadersGen9Conversion.xml +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/assimp.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/build_types.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/collision.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/gen9.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/gen9_shader_enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/gen9_shader_info.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/materials.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_joints.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_lights.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_materials.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/read_skeleton.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/rigging.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/shader_enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/shader_info.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/shaders.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_buffers.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_drawable.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_geometry.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_joints.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_lights.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_materials.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_models.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ydr/write_skeleton.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/audit.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/constants.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/instructions.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/ped.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yed/writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/cloth.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/cloth_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/cloth_writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/constants.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/corpus.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/drawable_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/drawables.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/events.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/events_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/events_writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/fields_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/fragment.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/fragment_drawable.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/glass.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/glass_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/glass_writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/io_helpers.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/matrices.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/matrices_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/matrices_writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics_authoring.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics_reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/physics_writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/pointers.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/stats.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/yft/validation.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/base.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/blocks.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/cargens.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/extension_defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/extensions.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/grass.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/io.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/lights.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/mlo_validation.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/occluders.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/packing.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/surfaces.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/timecycle.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymap/utils.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/builder.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/enums.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/pso.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/resource.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/schema.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymf/utils.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/ped_metadata.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/ped_variation.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/scenario.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ymt/streaming.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/heightmaps.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/network.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/regions.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynd/writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/assimp.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/reader.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ynv/writer.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytd/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytd/defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytd/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/__init__.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/archetypes.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/asset_types.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/base_archetype.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/extension_defs.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/extensions.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/flags.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/helpers.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/lod.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/mlo_validation.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury/ytyp/model.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/dependency_links.txt +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/requires.txt +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/fivefury.egg-info/top_level.txt +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_algorithms.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_algorithms.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_python.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_python.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/bounds_types.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/crypto_magic.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/crypto_magic.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_bindings.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_bounds.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_module.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_resource.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_rpf.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/py_vertex.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/resource_layout.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/resource_layout.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_archive.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_crypto.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_index.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_index.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_read.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_scan.cpp +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/native/rpf_scan.h +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/setup.cfg +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cdr.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cut.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cut_dsl.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_cut_no_template.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_dlc.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_drawable.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_gtxd.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_hashing.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_meta_rpf_contracts.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_mlo.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_rpf_ps3.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_rpf_streaming_folder.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_vehiclemeta.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_water.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ybn.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ycd.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ycd_cutscene_builder.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydd.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydr.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ydr_shaders.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_yft.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ymf.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ynd.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ynv.py +0 -0
- {fivefury-0.2.8 → fivefury-0.2.9}/tests/test_ynv_assimp.py +0 -0
- {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.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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":
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
+
]
|