FiveFury is a Python library for authoring, reading, writing, indexing, and packaging GTA V asset files.
It focuses on practical modding workflows: building drawable assets, collision resources, map metadata, animation dictionaries, nav data, texture dictionaries, text tables, audio containers, cutscenes, DLC metadata, and RPF archives from Python without forcing every user to work directly with binary layouts.
- Read, edit, build, and write core GTA V formats such as
YDR,YDD,YFT,YBN,YCD,YMAP,YTYP,YMF,YMT,YTD,YND,YNV,CUT,GXT2,AWC,REL, andRPF. - Use declarative high-level helpers for common authoring tasks while still keeping access to lower-level binary/resource details.
- Index game installs, loose folders, and archives with
GameFileCache, including typed lookups by asset name, hash, format, and lazy dictionaries for common asset families. - Extract texture dictionaries from
YTD,GTXDparent chains, and embedded dictionaries in drawable, fragment, particle, and ped component resources. - Build DLC metadata, map manifests, cutscenes, navigation cells, collision resources, fragment physics, and audio containers from Python.
- Share common
RSC7,META,PSO,RBF, XML, hashing, vector math, material, bounds, resource, and archive layers across formats. - Use optional native acceleration for heavier bounds, hashing, crypto, resource layout, and archive operations when the compiled extension is available.
pip install fivefuryFor local development from a checkout:
pip install -e .Python 3.11+ is required.
Assimp-backed import helpers such as assimp_to_ydr(...), obj_to_ydr(...), fbx_to_ydr(...), and obj_to_nav(...) also require:
- the Python package
impasse - a working native
assimplibrary discoverable by the current process
FiveFury does not currently probe common install locations on its own. The native library must already be reachable through the environment, usually via PATH.
FiveFury is released under the CC0-1.0 public domain dedication. See LICENSE.
Support levels:
| Status | Meaning |
|---|---|
| Full | Has practical read/write support and public high-level helpers for normal workflows. |
| Partial | Recognized or parsed enough for selected workflows, but not complete authoring support. |
| Indexed | Detected by GameFileCache and RPF tooling, but no dedicated high-level parser/writer yet. |
| Not implemented | Known GTA V format, but FiveFury does not currently expose dedicated support. |
| Format | Scope |
|---|---|
YDR |
Drawable resources: materials, shaders, samplers, numeric parameters, drawable models, LODs, render masks, lights, embedded textures, embedded bounds, skeletons, skinning, radial weight generation, rigid bone bindings, shader inspection, and skeleton hash recalculation. |
YDD |
Drawable dictionaries with multiple embedded drawables, high-level creation from named YDR drawables, and external-skeleton radial rigging helpers for ped components. |
YBN |
Bounds/collisions: primitive bounds, composite bounds, geometry bounds, BVH bounds, octants, material names, material colors, and generated collision chunks from triangle meshes. |
YCD |
Clip dictionaries: parsed metadata, sequence rebuilds, known track types, UV clip bindings, object animation metadata, skeletal tracks, root motion, camera tracks, and facial samples. |
YMAP |
Map metadata: entities, car generators, timecycle modifiers, occluders, content flags, entity flags, LOD lights, distant lights, and typed metadata. |
YTYP |
Archetypes: base/time/MLO archetypes, extensions, rooms, portals, entity sets, typed asset metadata, flags, LOD distances, physics dictionaries, and cutscene prop helpers. |
YMF |
Map manifests: CPackFileMetaData read/write, IMAP/ITYP dependency relationships, IMAP groups, interior bounds, HD texture bindings, relationship iteration, and manifest generation from YMAP sets with optional GameFileCache archetype lookup. |
YTD |
Texture dictionaries: read/write, resource texture payload preservation, cache extraction, and embedded-asset helpers. |
YND |
Path node resources: nodes, links, typed flags/enums, area helpers, automatic area ID calculation, network partitioning, and game-aligned junction heightmap generation. |
YNV |
Navmesh resources: sectors, polys, points, portals, typed metadata, validation, and basic Assimp/OBJ partitioning. |
CUT |
Cutscene files: cameras, tracks, events, props, peds, vehicles, lights, high-level scene conversion, .cuts script authoring, and .cut to .cuts export. |
GXT2 |
Hashed UTF-8 text tables with binary read/write, CodeWalker-style text import/export, mapping-style helpers, and GameFileCache loading. |
AWC |
Audio wave containers: structural read/write, PCM and WAV extraction, mono and multichannel PCM authoring, and conversion from .wav, .mp3, .ogg, and .flac through miniaudio. |
| DLC metadata | Declarative setup2.xml, content.xml, dlclist.xml, and extratitleupdatedata.meta authoring, including content change sets, DLC pack RPF creation, and dlc_patch overlays. |
GTXD metadata |
Parent texture dictionary metadata in XML or binary RBF CMapParentTxds form, cache loading, parent-chain resolution, and duplicate-safe relationship editing. |
RPF |
RPF7 OPEN archives, nested .rpf, folder/ZIP conversion, extraction modes, and encrypted standalone RPF opening when keys are available. |
| Format | Current behavior |
|---|---|
YFT |
Fragment reading/writing for common, damaged, extra and cloth drawables, including geometry, materials, LOD meshes, bounding sphere metadata, fragment flags, physics LODs, physics groups, physics children, child entity drawables, per-child breaking/inertia data, damping constants, damping archetypes, articulated body metadata, link attachments, group and child event references, editable composite bounds, mass/inertia helpers, glass/cloth/vehicle semantic queries, corpus scanning, validation, declarative physics helpers, geometry summaries and embedded texture dictionaries. |
YPT |
Resource texture dictionaries can be discovered/extracted from particle dictionaries, but full particle authoring is not implemented. |
REL |
Audio metadata banks can be read/written structurally, opened through GameFileCache, and round-tripped with unknown entries preserved. dat10.rel modular synth presets/synths, dat16.rel curves, dat22.rel categories, and common dat54.rel sound graph entries have typed models, including simple AWC-backed sounds, wrappers, sequential/multitrack/streaming child lists, randomized variations, modular synth sounds, automation/MIDI sounds, note maps, variable-curve and conditional routing, directional/kinetic routing, variable blocks, math operations, parameter transforms, fluctuators, external streams, sound sets, sound-set lists, and sound-hash lists. Other REL item families currently stay as raw entries. |
YED |
Expression dictionaries can be detected, opened through GameFileCache, inspected for expressions/tracks/streams/springs/instruction opcodes, edited safely for spring-list cloning, built from scratch for spring dictionaries, and validated before writing. |
YMT |
Generic META-backed read/write plus typed helpers for known roots such as CMapParentTxds, scenario manifests/regions/groups, ped variations, ped init metadata, and streaming request records. Unknown RBF/PSO/META payloads are preserved conservatively. |
RBF metadata |
Generic binary RBF parsing is exposed for metadata containers that use RBF0. It is a shared metadata layer, not a standalone GTA asset extension. |
YWR, YVR |
Recognized/indexed by GameFileCache and RPF tooling, but no complete dedicated high-level reader/writer is exposed. |
| Format family | Notes |
|---|---|
YFD, YPDB, MRF |
Known game file types, currently no dedicated high-level support. |
| Heightmap and watermap resources | Recognized as game concepts, but no complete public reader/writer yet. |
| Vehicle/ped audio REL specializations | REL files can be loaded structurally, but specialized semantic authoring beyond the initial synth/curve/category/sound subset is not currently exposed. |
fivefury.awc can decode common desktop audio formats through miniaudio and write PCM .awc files. Mono input is written as a normal single-channel AWC; stereo or multichannel input is written as a real multichannel AWC with a STREAM_FORMAT source stream and logical channel streams.
from fivefury import Awc, convert_audio_to_awc
# Direct file-to-file conversion. The stream name defaults to the source stem.
convert_audio_to_awc("music/stinger.mp3", "stream/stinger.awc")
# Force stereo output if the source is mono or has more channels than you need.
convert_audio_to_awc("music/song.flac", "stream/song.awc", channels=2)
# In-memory authoring when you also need to inspect or post-process the AWC.
awc = Awc.from_audio("radio_intro", "audio/radio_intro.ogg")
awc.save("stream/radio_intro.awc")The converter currently normalizes input to signed 16-bit PCM and preserves the source channel count unless channels= is provided. Use .rel metadata to expose the resulting .awc stream as a playable sound, radio entry, cutscene audio, or other game audio object.
.cuts is FiveFury's readable cutscene authoring format. It can compile back to .cut, and existing .cut files can be exported to .cuts for inspection or editing.
from fivefury import GameFileCache, save_cut_as_cutscript, save_cutscript
# Export a binary cutscene to a readable script.
save_cut_as_cutscript("stream/sample.cut", "stream/sample.cuts")
# Optional: resolve more hashes by scanning a game/resource folder first.
cache = GameFileCache("stream")
cache.scan()
cache.populate_resolver()
save_cut_as_cutscript("stream/sample.cut", "stream/sample_resolved.cuts")
# Compile the script back to a binary .cut.
save_cutscript("stream/sample.cuts", destination="stream/sample_from_script.cut")The exporter resolves known hashes through HashResolver and automatically registers sibling filenames when the source is a path. Unknown hashes stay as safe 0x???????? tokens. It also preserves cutscene flags, camera quaternions, and high-level streamed-model metadata such as CNAME, ANIM_BASE, ANIM_STREAMING_BASE, animation export specs, and typeFile/YTYP.
CutScript distinguishes static and animated cutscene props:
STATIC_PROP stage:
MODEL stage01
YTYP sample_meta
ANIMATED_PROP miku:
MODEL miku_hatsune_metal
YTYP sample_meta
CNAME mmd_model_001
ANIM_BASE miku_hatsune_metal
PRESET COMMON_PROP
MODEL is the streamed .ydr asset. CNAME is the logical cutscene/YCD binding name; it may match MODEL, but only when the YCD was authored with the same object name.
The preferred high-level authoring style is now:
add_*for collectionsset_*for single assignments or bindingsbuild()to normalize derived state before serializationvalidate()to collect consistency issues
Enums are preferred where the game format has stable names: shaders, LODs, render masks, archetype asset types, bound material types, YND flags, YCD track formats, and skeleton flag-name mappings all expose typed values on the public API.
Some newer high-level helpers were renamed to match that convention. If you were using recent pre-release YDR helpers, notable renames are:
create_bone(...)->add_bone(...)embed_texture(...)->add_embedded_texture(...)unembed_texture(...)->remove_embedded_texture(...)use_bound(...)->set_bound(...)skin_model(...)->set_model_skin(...)
from fivefury import Ymap
ymap = Ymap(name="example_map")
# Entities
ymap.entity("prop_tree_pine_01", position=(100, 200, 0), lod_dist=150.0)
ymap.entity("prop_bench_01a", position=(105, 200, 0), lod_dist=80.0)
# Car generators
ymap.car_gen("sultan", (110, 205, 0), heading=90)
ymap.car_gen("adder", (115, 205, 0), heading=90, body_colors=(5, 10), livery=2)
# Time cycle modifiers (center + size)
ymap.time_cycle_modifier("interior_dark", (100, 200, 5), (50, 50, 20), hours=(20, 6))
# Box occluders (position + size + angle in degrees)
ymap.box_occluder(position=(100, 200, 0), size=(10, 10, 10), angle=45)
# Occlude models
ymap.occlude_box((-5, -5, 0), (5, 5, 10))
ymap.occlude_quad([(0, 0, 0), (10, 0, 0), (10, 0, 10), (0, 0, 10)])
ymap.save("example_map.ymap", auto_extents=True)If you want an internal resource path, set ymap.resource_name before saving.
from pathlib import Path
from fivefury import Ymap
ymap = Ymap.from_bytes(Path("example_map.ymap").read_bytes())
print(len(ymap.entities))
print(len(ymap.car_generators))
print(ymap.flags, ymap.content_flags)
for cg in ymap.car_generators:
print(cg.car_model, cg.heading, cg.body_colors)from fivefury import Archetype, ArchetypeAssetType, ParticleEffectExtension, Ytyp
ytyp = Ytyp(name="example_types")
archetype = Archetype(
name="prop_tree_pine_01",
lod_dist=150.0,
asset_type=ArchetypeAssetType.DRAWABLE,
bb_min=(-1.5, -1.5, -0.5),
bb_max=(1.5, 1.5, 8.0),
bs_centre=(0.0, 0.0, 3.5),
bs_radius=5.0,
)
archetype.add_extension(
ParticleEffectExtension(
name="fx_tree",
fx_name="scr_wheel_burnout",
fx_type=2,
scale=0.8,
)
)
ytyp.add_archetype(archetype)
ytyp.save("example_types.ytyp")from fivefury import Ymap, create_rpf
ymap = Ymap(name="packed_map")
ymap.entity("prop_tree_pine_01", position=(0.0, 0.0, 0.0), lod_dist=120.0)
archive = create_rpf("mods.rpf")
archive.add("stream/packed_map.ymap", ymap)
archive.add("docs/readme.txt", b"hello from fivefury")
archive.save("mods.rpf")from fivefury import write_dlc_folder_metadata
# The folder is the extracted root that will become dlc.rpf.
metadata = write_dlc_folder_metadata(
"build/my_pack",
pack_name="my_pack",
order=60,
)
print(metadata.setup.device_name)
print(len(metadata.content.data_files))The helper scans the folder, ignores dot-prefixed folders, infers common DLC entries such as nested .rpf files, .ityp requests, audio .dat files, overlayinfo.xml, interiorProxies.meta, dlctext.meta, and gtxd.meta, then writes setup2.xml and content.xml.
content.xml is the retail GTA V name. If a toolchain needs a different metadata filename, pass dat_file="context.xml"; setup2.xml will point to that file.
write_dlc_folder_metadata("build/my_pack", dat_file="context.xml")from fivefury import DlcContentGroup, DlcPatch
patch = DlcPatch("my_pack")
patch.content.rpf("dlc_my_pack:/x64/levels/gta5/LODLights.rpf", map_data=True)
patch.change_set("MY_PACK_PATCH_MAP", group=DlcContentGroup.MAP)
patch.save_update_rpf("update.rpf")DlcPatch writes update:/dlc_patch/<pack>/setup2.xml, content.xml, patch payloads, and a matching common/data/extratitleupdatedata.meta mount entry. The patch mount uses the original DLC deviceName, matching the title-update overlay behavior used by the game.
from fivefury import GameFileCache, create_ymf_for_ymaps, read_ymap, read_ytyp
ymap = read_ymap("stream/custom_city.ymap")
ytyp = read_ytyp("stream/custom_city.ytyp")
manifest = create_ymf_for_ymaps(
[ymap],
ytyps=[ytyp],
name="_manifest",
strict=True,
)
manifest.save("stream/_manifest.ymf")If your custom map uses vanilla archetypes, pass a scanned GameFileCache so FiveFury can resolve the IMAP to ITYP relationships from the indexed game data:
cache = GameFileCache(r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V")
cache.scan_game(use_index_cache=True)
manifest = cache.create_ymf_for_ymaps(["stream/custom_city.ymap"], name="_manifest")
manifest.save("stream/_manifest.ymf")The default manifest name is _manifest, matching the convention used by streamed map packs.
from fivefury import RpfExportMode, rpf_to_folder, rpf_to_zip, zip_to_rpf
zip_to_rpf("unpacked_mod_folder", "packed_mod.rpf")
rpf_to_zip("packed_mod.rpf", "packed_mod.zip", mode=RpfExportMode.STANDALONE)
rpf_to_folder("packed_mod.rpf", "packed_mod", mode=RpfExportMode.STANDALONE)Directories ending in .rpf are packed as nested archives.
from fivefury import RpfArchive
archive = RpfArchive.from_path(r"C:\mods\dlc.rpf")
print(len(archive.all_entries))Encrypted standalone archives can be opened directly. FiveFury initializes the bundled GTA V crypto context automatically.
from fivefury import RpfArchive, RpfExportMode
archive = RpfArchive.from_path("packed_mod.rpf")
archive.to_folder("out_standalone", mode=RpfExportMode.STANDALONE)
archive.to_folder("out_logical", mode=RpfExportMode.LOGICAL)
archive.to_zip("out_stored.zip", mode=RpfExportMode.STORED)
print(RpfExportMode.STANDALONE.description)RpfExportMode controls what gets written:
STORED: raw entry bytes as stored in the archiveSTANDALONE: valid standalone files, includingRSC7containers for resourcesLOGICAL: logical payloads with resource containers removed
from fivefury import BoundSphere, BoundType, TextureFormat, read_ydr
ydr = read_ydr("prop_example.ydr")
print(ydr.model_count)
print(len(ydr.lights))
print(ydr.materials[0].shader_name)
ydr.update_material(
0,
shader="spec.sps",
textures={
"DiffuseSampler": "prop_example_d",
"SpecSampler": "prop_example_s",
"BumpSampler": None,
},
parameters={
"specularIntensityMult": 2.0,
},
)
ydr.add_embedded_texture(
name="prop_example_d",
data=bytes([255, 255, 255, 255] * 16),
width=4,
height=4,
format=TextureFormat.A8R8G8B8,
)
ydr.set_bound(
BoundSphere(
bound_type=BoundType.SPHERE,
box_min=(-0.5, -0.5, -0.5),
box_max=(0.5, 0.5, 0.5),
box_center=(0.0, 0.0, 0.0),
sphere_center=(0.0, 0.0, 0.0),
sphere_radius=0.75,
margin=0.05,
)
)
issues = ydr.validate()
print(issues)
ydr.save("prop_example_out.ydr")FiveFury exposes:
- global
ydr.materials - per-model views through
ydr.models - parsed
ydr.lights - editable material shaders, samplers, and numeric parameters
- embedded texture helpers through
add_embedded_texture(...)andremove_embedded_texture(...) - embedded collision helpers through
set_bound(...)andclear_bound() - skeleton helpers for bones, skinning, radial weight generation, rigid bone bindings, and explicit skeleton hash recalculation
build()/validate()helpers for authoring flows
from fivefury import RadialBoneRigRule, read_ydd, rig_ydd_to_bones_radially
body = read_ydd("tdev_xyuls^lowr_000_u.ydd")
skeleton_source = read_ydd("tdev_xyuls^head_000_u.ydd")
report = rig_ydd_to_bones_radially(
body,
[
RadialBoneRigRule("SM_R_BackSkirtRoll", radius=0.16, strength=0.65),
RadialBoneRigRule("SM_L_BackSkirtRoll", radius=0.16, strength=0.65),
],
skeleton_source=skeleton_source,
)
print(report.vertices)
body.save("tdev_xyuls^lowr_000_u_rigged.ydd")For body folders where head_000_u.ydd carries the skeleton and uppr/lowr carry the meshes, use the convenience pass:
from fivefury import rig_body_folder_jiggle_bones
report = rig_body_folder_jiggle_bones(
r"C:\mods\body",
output_folder=r"C:\mods\body_rigged",
)
print(report.saved_files)The helper preserves existing skinning and reuses ped-component palettes that already store external skeleton indices. It only adds or adjusts vertex influences around the requested jiggle bones; it does not generate cloth simulation data by itself.
from fivefury import read_ydr
ydr = read_ydr("weapon_example.ydr")
root = ydr.add_bone("root", tag=0)
child = ydr.add_bone("child", parent=root, tag=1)
ydr.ensure_skeleton().build()
ydr.set_model_skin(0, bone_index=0, palette_size=0xFF)
mesh = ydr.meshes[0]
mesh.set_skin(
bone_ids=[root, child],
weights=[
(1.0, 0.0, 0.0, 0.0),
(0.5, 0.5, 0.0, 0.0),
(0.0, 1.0, 0.0, 0.0),
],
indices=[
(0, 0, 0, 0),
(0, 1, 0, 0),
(1, 0, 0, 0),
],
)
print(ydr.validate())
ydr.save("weapon_example_out.ydr")Some animated YDRs, especially rigid object rigs where drawable models are bound to bones without vertex weights, need skeleton hash fields derived from bone tags, flags, and transforms. FiveFury preserves existing values by default for safe read/edit/write roundtrips. When authoring a skeleton from scratch, opt in explicitly:
from fivefury import YdrBoneFlags, YdrSkeleton, YdrSkeletonBinding, create_ydr
skeleton = YdrSkeleton.create()
root = skeleton.add_bone(
"root",
tag=0,
flags=YdrBoneFlags.ROT_X | YdrBoneFlags.ROT_Y | YdrBoneFlags.ROT_Z,
)
skeleton.add_bone(
"moving_part",
parent=root,
tag=1,
flags=YdrBoneFlags.ROT_X | YdrBoneFlags.TRANS_Y,
translation=(0.0, 0.25, 0.0),
)
skeleton.build()
build = create_ydr(
meshes=[...],
material_textures={"DiffuseSampler": "animated_prop_d"},
skeleton=skeleton,
skeleton_binding=YdrSkeletonBinding.rigid(bone_index=0),
name="animated_prop",
)
# Recalculate only for this write. The in-memory skeleton is not mutated.
build.save("animated_prop.ydr", recalculate_skeleton_hashes=True)If you want to store the values on the skeleton object before writing:
from fivefury import calculate_skeleton_unknown_hashes
hashes = calculate_skeleton_unknown_hashes(skeleton)
print(hashes)
skeleton.recalculate_unknown_hashes()
build.save("animated_prop.ydr")The formal flag-name mapping used by the hash helper is exposed through YdrBoneFlagName and skeleton_bone_flag_names(...).
from fivefury import YdrLight, YdrMeshInput, create_ydr
ydr = create_ydr(
meshes=[
YdrMeshInput(
positions=[(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)],
indices=[0, 1, 2],
texcoords=[[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]],
)
],
material_textures={"DiffuseSampler": "example_diffuse"},
lights=[YdrLight.point(position=(0.0, 0.0, 5.0), intensity=3.0)],
name="example_drawable",
)
ydr.add_light(YdrLight.spot(
position=(0.0, 2.0, 5.0),
direction=(0.0, 0.0, -1.0),
cone_outer_angle=0.6,
))
ydr.save("example_drawable.ydr")from fivefury import assimp_to_ydr, obj_to_ydr
assimp_to_ydr(
r"C:\mods\example.fbx",
r"C:\mods\example.ydr",
generate_ytyp=True,
)
obj_to_ydr(
r"C:\mods\example.obj",
r"C:\mods\example_obj.ydr",
)assimp_to_ydr(...) is now the unified import path for any source format that Assimp can read. obj_to_ydr(...) and fbx_to_ydr(...) are thin wrappers over that same pipeline.
This can also emit a companion YTYP with lowercase naming and textureDictionary set to <model>_txd.
These helpers require impasse plus a native assimp library that is already discoverable by the current process.
from fivefury import YdrShader, print_ydr_shader_info, read_ydr
print_ydr_shader_info(YdrShader.NORMAL_SPEC_CUTOUT)
ydr = read_ydr("prop_example.ydr")
ydr.update_material(
0,
shader=YdrShader.NORMAL_SPEC_CUTOUT,
textures={
"DiffuseSampler": "prop_example_d",
"BumpSampler": "prop_example_n",
"SpecSampler": "prop_example_s",
},
)
ydr.save("prop_example_cutout.ydr")YdrShader is generated from the bundled shader definitions, so IDEs can autocomplete known .sps names. Shader info helpers expose render bucket, vertex layout, texture slots, and numeric parameters. If an authoring path provides SpecularSampler, FiveFury normalizes it to the drawable slot name SpecSampler.
from fivefury import Ydd, read_ydd
ydd = read_ydd("uppr_001_u.ydd")
for entry in ydd.iter_drawables():
drawable = entry.drawable
print(entry.name, drawable.model_count, len(drawable.materials))
out = Ydd.from_drawables({ydd.drawables[0].name: ydd.drawables[0].drawable}, version=165)
out.save("single_drawable.ydd")YFT fragment support is aimed at practical read/edit/write workflows for objects with drawable variants and physics metadata. It shares the same drawable writer used by YDR, and the same bounds model used by YBN.
from fivefury import read_yft
yft = read_yft("prop_vehicle_fragment.yft")
print(yft.name)
print(yft.bounding_sphere)
print(yft.geometry_stats())
for issue in yft.validate():
print(issue.severity, issue.message)
for child in yft.iter_physics_children():
print(child.owner_group_name, child.undamaged_mass, child.undamaged_ang_inertia)from fivefury import BoundBox, BoundMaterialType, create_yft, read_ydr, save_yft
drawable = read_ydr("crate.ydr")
physics_bound = BoundBox.from_center_size(
center=(0.0, 0.0, 0.5),
size=(1.0, 1.0, 1.0),
material_index=BoundMaterialType.WOOD_SOLID_MEDIUM,
)
yft = create_yft(
drawable,
name="crate_fragment",
physics_bound=physics_bound,
physics_density=0.65,
)
yft.validate()
save_yft(yft, "crate_fragment.yft")Current YFT authoring covers common fragment structure, embedded drawables, geometry and material payloads, fragment flags, bounding sphere metadata, physics LODs, groups, children, damping, articulated body metadata, event refs, mass/inertia helpers, editable composite bounds, and embedded texture dictionaries. Vehicle-specific behavior, advanced damage tuning, and every unknown fragment field are still conservative.
from fivefury import BoundBox, BoundMaterialType, Ybn
bound = BoundBox.from_center_size(
center=(0.0, 0.0, 1.0),
size=(4.0, 4.0, 2.0),
material_index=BoundMaterialType.CONCRETE,
)
ybn = Ybn.from_bound(bound)
print(ybn.validate())
ybn.save("simple_collision.ybn")Primitive helpers are available for BoundSphere, BoundBox, BoundDisc, BoundCylinder, and BoundCloth. Material indices accept BoundMaterialType enum values instead of requiring raw integers.
from fivefury import BoundMaterial, BoundMaterialType, build_bound_from_triangles, save_ybn
triangles = [
((0.0, 0.0, 0.0), (4.0, 0.0, 0.0), (0.0, 4.0, 0.0)),
((4.0, 0.0, 0.0), (4.0, 4.0, 0.0), (0.0, 4.0, 0.0)),
]
bound = build_bound_from_triangles(
triangles,
material=BoundMaterial(type=BoundMaterialType.CONCRETE),
)
save_ybn(bound, "floor_collision.ybn")Generated geometry is chunked when needed, gets BVH data, and includes octants for BoundGeometry children. The same bounds model is used by standalone YBN files and embedded YDR collisions.
from fivefury import read_ycd
ycd = read_ycd("maude_mcs_1-0.ycd")
print(len(ycd.clips))
print(len(ycd.animations))
print(ycd.clips[0].short_name)
print(ycd.animations[0].duration)
ycd.build()
ycd.save("maude_mcs_1-0_roundtrip.ycd")FiveFury preserves parsed clip and animation metadata, rebuilds sequence data through typed channels, and hardens known skeletal/object animation fields before export. UV clips use the runtime binding convention <object>_uv_<slot_index> and MetaHash(object) + slot_index + 1.
from fivefury import build_ycd_uv_clip_hash, build_ycd_uv_clip_name, create_ycd_uv_clip
clip_name = build_ycd_uv_clip_name("prop_sign", 0)
clip_hash = build_ycd_uv_clip_hash("prop_sign", 0)
clip = create_ycd_uv_clip(object_name="prop_sign", slot_index=0, start_time=0.0, end_time=1.0)
print(clip_name, clip_hash, clip.short_name)from fivefury import YndLink, YndNetwork, YndNode
node_a = YndNode(key="a", position=(0.0, 0.0, 0.0))
node_b = YndNode(key="b", position=(600.0, 0.0, 0.0))
node_a.links.append(YndLink(target_key="b"))
node_b.links.append(YndLink(target_key="a"))
for ynd in YndNetwork.from_nodes([node_a, node_b]).build_ynds():
ynd.save(f"nodes_{ynd.area_id}.ynd")YndNetwork computes each node's area_id from its world position, assigns local node IDs per area, and resolves links by target_key. Use Ynd.from_nodes(...) directly when you already know all nodes belong to one area.
from fivefury import YndNode
node = YndNode(position=(0.0, 0.0, 0.0))
node.ensure_junction_heightmap(
triangles=[
((-1.0, -1.0, 0.0), (1.0, -1.0, 0.25), (-1.0, 1.0, 0.25)),
((1.0, -1.0, 0.25), (1.0, 1.0, 0.5), (-1.0, 1.0, 0.25)),
],
bounds=((-1.0, -1.0), (1.0, 1.0)),
dim_x=2,
dim_y=2,
)YND junction heightmaps follow the runtime layout used by GTA V virtual junctions: position stores the minimum X/Y sample origin, samples are row-major, the default grid spacing is 2.0 world units, and byte values decode as min_z + byte * ((max_z - min_z) / 256.0).
from fivefury import read_ynv
ynv = read_ynv("navmesh[120][120].ynv")
print(ynv.area_id)
print(len(ynv.polys))
print(len(ynv.vertices))
print(ynv.validate())YNV support currently includes:
- typed
YnvAdjacencyType,YnvPointType, andYnvPortalType - editable
vertices,indices,edges,polys,portals, andsector_tree build()to normalize derived fields such aspoints_start_idand content flagsvalidate()to catch invalid poly spans, portal-link spans, and sector metadata mismatches before writing
from fivefury import obj_to_nav
paths = obj_to_nav(
"test.obj",
"out_navmeshes",
)
print(len(paths))
print(paths[0].name)obj_to_nav(...) is a simple Assimp-backed helper that:
- reads geometry through the shared Assimp pipeline
- clips triangles against GTA V navmesh cells
- writes one
YNVper touched cell - names outputs as
navmesh[file_x][file_y].ynv
This is intentionally a basic geometry partitioner, not a full navgen pipeline. It does not yet generate advanced navigation semantics such as cover, climb/drop adjacencies, portals, or point placement.
YED files are expression dictionaries used by peds through expression set metadata. FiveFury exposes expressions, typed tracks, streams, semantic instruction operands, variables, and spring blocks:
from fivefury import YedTrackFormat, read_yed
yed = read_yed("ambient.yed")
breasts = yed.require_expression("breasts")
print(breasts.spring_bone_ids)
print([track.format for track in breasts.tracks])
for stream in breasts.streams:
for instruction in stream.instructions:
print(instruction.name, instruction.operands)Small spring dictionaries can be built declaratively:
from fivefury import YedTrackFormat, create_yed, save_yed
yed = create_yed("breasts")
expr = yed.require_expression("breasts")
expr.ensure_spring(0xFC8E)
expr.ensure_spring(0x885F)
expr.ensure_track(0xFC8E, format=YedTrackFormat.VECTOR3)
yed.validate()
save_yed(yed, "ambient_custom.yed")Existing spring descriptions can be cloned when a custom skeleton keeps the same physics shape but adds new bone tags:
yed.clone_breast_springs_to_glutes(
left_breast=0xFC8E,
right_breast=0x885F,
left_glute=0x40B2,
right_glute=0xC141,
)Complex expression streams are currently preserved and decoded at opcode level. Full semantic editing of every stream instruction is intentionally more conservative because those bytecode operands need to stay 1:1 with the game VM. Streams can also be authored with semantic instructions for the supported VM layouts:
from fivefury import YedInstruction, YedInstructionType, YedStream
expr = yed.ensure_expression("face")
expr.streams.append(YedStream.raw_stream("main", depth=2, data3=b""))
expr.streams[0].instructions = [
YedInstruction(YedInstructionType.PUSH_FLOAT, operands={"value": 1.0}),
YedInstruction(YedInstructionType.PUSH_VECTOR, operands={"value": (1.0, 0.0, 0.0, 0.0)}),
YedInstruction(YedInstructionType.END),
]The supported semantic layouts currently cover empty stack/vector ops, float/vector constants, bone track ops, variables, jumps, springs, look-at, and blend op payloads. Unknown or malformed bytecode is still preserved from existing files, but validation reports it before semantic rebuilds.
FiveFury exposes a few metadata layers directly because several GTA V formats share them internally.
from fivefury import read_gtxd
gtxd = read_gtxd("gtxd.ymt")
print(gtxd.source) # "xml" or "rbf"
print(gtxd.parent_of("custom_asset_txd"))
print(list(gtxd.iter_chain("custom_asset_txd")))GTXD data maps child texture dictionaries to parent dictionaries. GameFileCache uses it when resolving textures for streamed assets, so a drawable can find textures in its own YTD, an explicitly assigned dictionary, or inherited parent dictionaries.
from fivefury import YmtContentType, read_ymt
ymt = read_ymt("peds.ymt")
print(ymt.format)
print(ymt.content_type)
if ymt.content_type is YmtContentType.PED_METADATA:
for item in ymt.ped_metadata.init_datas:
print(item.clip_dictionary_name, item.expression_dictionary_name)YMT support is intentionally layered: known roots get typed helpers, while unknown META/PSO/RBF data remains available for safe roundtrips instead of being discarded.
from fivefury import GameFileCache
cache = GameFileCache(
r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
scan_workers=8,
max_loaded_files=16,
)
cache.scan_game(use_index_cache=True)
print(cache.asset_count)
print(cache.stats_by_kind())GameFileCache indexes loose files and archive contents, then loads supported formats lazily.
from fivefury import GameFileCache
cache = GameFileCache(
r"C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
dlc_level="mpbattle",
exclude_folders="mods;scratch",
load_audio=False,
load_vehicles=True,
load_peds=True,
)
cache.scan_game(use_index_cache=True)Useful scan options:
dlc_level: limit active DLCsexclude_folders: ignore folders by prefixload_audio: skip audio-related assets during scanload_vehicles: skip vehicle-related assets during scanload_peds: skip ped-related assets during scanuse_index_cache: reuse the persisted scan index for faster startup
asset = cache.get_asset("prop_tree_pine_01", kind=".ydr")
print(asset.path)
print(asset.short_name_hash)You can iterate the cache directly:
for asset in cache:
print(asset.path, asset.kind)Or iterate a specific kind:
for ydr in cache.iter_kind(".ydr"):
print(ydr.path)from pathlib import Path
asset = cache.get_asset("prop_tree_pine_01", kind=".ydr")
data = cache.read_bytes(asset, logical=True)
out_path = cache.extract_asset(asset, Path("prop_tree_pine_01.ydr"))
print(len(data))
print(out_path)Common access patterns:
get_asset(...): resolve one asset by path, name or hashread_bytes(...): get bytes directlyget_file(...): build a lazyGameFilewrapperextract_asset(...): write the asset to disk
Extraction defaults to standalone file output.
For resource assets such as YDR, YDD, YFT, YTD, YMAP and YTYP, this produces a valid standalone RSC7 file.
If you want the logical payload instead:
cache.extract_asset("prop_tree_pine_01", "prop_tree_pine_01_payload.ydr", logical=True)GameFileCache can resolve textures from:
- direct
YTDfiles texture_dictionaryreferences fromYTYParchetypes- parent relationships from
gtxd.meta - embedded texture dictionaries inside
YDR,YDD,YFTandYPT
from pathlib import Path
paths = cache.extract_asset_textures(
"stt_prop_stunt_bowling_pin.yft",
Path("bowling_pin_textures"),
)
for path in paths:
print(path)You can inspect the texture refs first:
for ref in cache.list_asset_textures("uppr_001_u.ydd"):
print(ref.origin, ref.container_name, ref.texture.name)GameFileCache exposes lazy type dictionaries keyed by shortNameHash.
from fivefury import jenk_hash
ydr = cache.YdrDict[jenk_hash("prop_tree_pine_01")]
ytd = cache.YtdDict[jenk_hash("vehshare")]
ybn = cache.YbnDict[jenk_hash("v_carshowroom")]Available dictionaries include YdrDict, YddDict, YtdDict, YmapDict, YtypDict, YftDict, YbnDict, YcdDict, YptDict, YndDict, YnvDict, YedDict, YwrDict, YvrDict, RelDict, Gxt2Dict, and AwcDict.
GameFileCache also builds a lazy global archetype lookup from indexed YTYP files.
archetype = cache.get_archetype("prop_tree_pine_01")
print(archetype.name)
for archetype in cache.iter_archetypes():
print(archetype.name)from fivefury import register_name, register_names_file, resolve_hash, jenk_hash
register_name("prop_tree_pine_01")
register_names_file("common_names.txt")
print(resolve_hash(jenk_hash("prop_tree_pine_01")))The resolver is shared and optional. It is useful for display, lookups and tooling.