Python library for GTA V asset workflows including YMAP, YTYP, RPF, YTD and GameFileCache utilities.
Project description
FiveFury
FiveFury is a Python library for GTA V asset workflows.
It provides practical support for:
YDRread/write for drawable workflows, including materials, shaders, drawable models, embedded textures, embedded collisions, skeletons, skinning, rigid bone bindings, and lightsYDDread/write support for drawable dictionaries with multiple embedded drawablesYCDread/write support for clip dictionaries, animation metadata, UV animation bindings, object tracks, skeletal tracks, camera tracks, root motion, and facial samplesYBNread/write support for bounds, collision materials, geometry, BVH data, octants, and composite boundsYNDread/write support for path nodes, links, flags, area helpers, and automatic network partitioningYNVread/write support for navmesh resources, typed poly/point/portal metadata, validation, and simple Assimp-based OBJ partitioningYMAPread/writeYTYPread/write with typed archetype, MLO, portal, room, extension, and flag helpersYTDread/write and texture extraction helpersRPF7 OPENarchives and nested.rpfZIP -> RPF,RPF -> ZIP, andRPF -> folder- opening encrypted standalone
.rpffiles without preloading game keys - fast asset indexing with
GameFileCache - texture extraction from
YTD,GTXDparent chains and embedded dictionaries inYDR,YDD,YFTandYPT - shared
RSC7,META, hashing, and binary helper layers used by the resource formats - optional native acceleration for heavier bounds and archive operations when the compiled extension is available
Installation
pip install fivefury
For 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.
API Style
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(...)
Current Format Coverage
This is the practical coverage exposed by the high-level API:
YDR: read, edit, build, and write drawable resources with materials, shaders, samplers, numeric parameters, drawable models, LODs, render masks, lights, embedded textures, embedded bounds, skeletons, skinning, rigid bone bindings, shader inspection, and skeleton hash recalculation.YDD: read and write drawable dictionaries, including creating a dictionary from namedYDRdrawables.YCD: read and write clip dictionaries, preserve parsed metadata, rebuild sequence data, evaluate known track types, create UV clip bindings, and harden skeletal/object animation metadata before export.YBNand bounds: read and write standalone collision resources, primitive bounds, composite bounds, geometry bounds, BVH bounds, octants, material names, material colors, and generated collision chunks from triangle meshes.YND: read and write nav/path node resources, preserve node/link metadata, use typed flags/enums, compute area IDs from positions, and split a high-level node network into per-areaYNDresources.YNV: read and write navmesh resources, preserve sector trees, use typed point and portal metadata, validate structural consistency, and generate basic per-cell navmeshes from Assimp geometry.YMAPandYTYP: author entities, car generators, timecycle modifiers, occluders, archetypes, extensions, MLO structures, flags, and typed asset metadata.YTD: read and write texture dictionaries, preserve resource texture payloads, and extract textures through cache and embedded-asset helpers.RPF: create, read, extract, convert, and pack archives, including nested.rpfdirectories and standalone resource extraction.
Quick Start
Create a YMAP
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.
Load a YMAP
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)
Create a YTYP
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")
Pack Assets into an RPF
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")
Convert between ZIP, RPF, and folders
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.
Open an encrypted standalone RPF
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.
Export mode overview
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
YDR
Read and edit a YDR
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, rigid bone bindings, and explicit skeleton hash recalculation
build()/validate()helpers for authoring flows
Skin a YDR model declaratively
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")
Write skeleton hashes for animated YDRs
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(...).
Create a simple YDR
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")
Convert Assimp-supported meshes to 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.
Inspect and choose YDR shaders
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.
Read and write a YDD
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")
YBN and Bounds
Create primitive bounds
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.
Build collision from triangles
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.
YCD
Read and write a YCD clip dictionary
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.
Create or inspect UV clip bindings
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)
YND
Build path nodes and partition by area
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.
YNV
Read and validate a YNV
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
Split an OBJ into per-cell navmeshes
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.
GameFileCache
Scan a Game Installation
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.
Control DLC and Scan Scope
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
Look Up Assets by Name and Type
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)
Read and Extract Assets
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)
Extract Textures for an Asset
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)
Type Dictionaries
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 and Gxt2Dict.
Archetype Lookup
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)
Global Hash Resolver
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fivefury-0.1.48.tar.gz.
File metadata
- Download URL: fivefury-0.1.48.tar.gz
- Upload date:
- Size: 756.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b424e495b8fe3ea0b6f9219b334fc2c3017d5d972ac3e5cc1445ca9766f801fb
|
|
| MD5 |
3657d803faadfe4a967119e0deac81a1
|
|
| BLAKE2b-256 |
f6c940cadf67f90f1d06c918b09b8213d3831b1e70c9b1aa6a216577a71f257d
|
File details
Details for the file fivefury-0.1.48-cp311-abi3-win_amd64.whl.
File metadata
- Download URL: fivefury-0.1.48-cp311-abi3-win_amd64.whl
- Upload date:
- Size: 810.1 kB
- Tags: CPython 3.11+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b695a8a76535d81d5df2a733243cf9defe474bf8b9d2b7d857689dce18469af8
|
|
| MD5 |
372d64406b62b88611e682dc208a252f
|
|
| BLAKE2b-256 |
8fea9ddfeb900295203c91ce0753d6149de31887df7424eae00b20744b3eac28
|