Python library for reading and writing GTA RenderWare DFF, TXD, and IMG files
Project description
rwfury
Python library for reading and writing GTA RenderWare DFF (3D model), TXD (texture dictionary), and IMG (archive) files. Supports GTA III, Vice City, and San Andreas.
Features
- DFF parsing with full plugin support: BinMesh, Skin, HAnim, 2dfx, Material Effects, Night Colors, Collision, Specular/Reflection materials
- DFF writing back to binary (round-trip)
- TXD parsing with DDS export and raw RGBA decoding
- IMG archives: read/write v1 (GTA III/VC) and v2 (San Andreas), extract files, parse DFF/TXD directly from memory
- Version-aware: handles RW 3.1 (GTA III) through 3.6 (San Andreas) struct differences automatically
- GenericMesh: format-agnostic mesh representation with flat arrays and byte-packing helpers for easy porting to glTF, FBX, or any custom format
- Zero external dependencies (pure Python, stdlib only)
Installation
pip install rwfury
Requires Python 3.10+. No external dependencies.
Quick start
Read files from an IMG archive
from rwfury import Img, Dff, Txd
# Open an IMG (auto-detects v1 or v2)
img = Img.from_file("gta3.img")
# List contents
print(f"{len(img.entries)} files")
for name in img.list_files()[:10]:
print(f" {name}")
# Find a file (case-insensitive)
entry = img.find("cop.dff")
print(f"{entry.name}: {entry.size} bytes")
# Parse DFF/TXD directly from IMG (no temp files)
dff = Dff.from_bytes(img.read("cop.dff"))
txd = Txd.from_bytes(img.read("cop.txd"))
# Extract files to disk
img.extract("cop.dff", "output/") # single file
img.extract_all("output/") # everything
Parse a DFF and extract meshes
from rwfury import Dff
dff = Dff.from_file("model.dff")
# High-level mesh extraction (grouped by material)
for entry in dff.get_meshes():
print(entry["name"], entry["texture"])
for mesh in entry["meshes"]:
print(f" {len(mesh.positions)} vertices, {len(mesh.indices)//3} triangles")
print(f" UV sets: {len(mesh.texcoords)}")
Format-agnostic export with GenericMesh
from rwfury import Dff
dff = Dff.from_file("model.dff")
for mesh in dff.to_generic_meshes():
# Flat arrays ready for GPU upload or any format conversion
pos_bytes = mesh.positions_as_bytes() # float32 LE, 3 per vertex
idx_bytes = mesh.indices_as_bytes("u16") # uint16 LE (or "u32")
uv_bytes = mesh.texcoords_as_bytes(0) # float32 LE, 2 per vertex
norm_bytes = mesh.normals_as_bytes() # float32 LE, 3 per vertex
# Material info
print(mesh.texture_name, mesh.diffuse_color)
# 4x4 transform matrix (row-major, 16 floats)
print(mesh.transform)
# Skinning data (if present)
if mesh.has_skinning:
bi = mesh.bone_indices_as_bytes() # uint8, 4 per vertex
bw = mesh.bone_weights_as_bytes() # float32, 4 per vertex
Extract textures from a TXD
from rwfury import Txd
txd = Txd.from_file("textures.txd")
# Export all textures as .dds files
txd.export_to_dds("output_folder/")
# Or get raw RGBA pixel data (for compression, processing, etc.)
for tex in txd.textures:
rgba_mipmaps, has_alpha = tex.to_rgba()
# rgba_mipmaps[0] = bytes, width * height * 4, R,G,B,A order
print(f"{tex.name}: {tex.width}x{tex.height}, alpha={has_alpha}")
Write a modified DFF
from rwfury import Dff
dff = Dff.from_file("original.dff")
# Modify frames, geometry, materials, etc.
dff.frames[0].name = "renamed_frame"
# Write back to binary
dff.to_file("modified.dff")
Create a new IMG archive
from rwfury import Img
# Build a v2 IMG from files
files = {
"model.dff": open("model.dff", "rb").read(),
"texture.txd": open("texture.txd", "rb").read(),
}
Img.create_v2(files, "output.img")
Access low-level data
from rwfury import Dff
dff = Dff.from_file("model.dff")
# Frames (skeleton/hierarchy)
for frame in dff.frames:
print(frame.name, frame.position, frame.parent)
if frame.hanim:
print(f" HAnim: {len(frame.hanim.bones)} bones")
# Geometry details
for geom in dff.geometries:
print(f"{len(geom.vertices)} verts, {len(geom.triangles)} tris")
print(f"UV sets: {geom.num_uv_sets}, flags: 0x{geom.flags:04X}")
# Plugins
if geom.bin_mesh:
print(f"BinMesh: {len(geom.bin_mesh.splits)} splits")
if geom.skin:
print(f"Skin: {geom.skin.num_bones} bones")
if geom.night_colors:
print(f"Night colors: {len(geom.night_colors)} vertices")
if geom.effects_2dfx:
print(f"2dfx: {len(geom.effects_2dfx)} effects")
# Materials
for mat in geom.materials:
print(f" {mat.texture_name} color={mat.color}")
if mat.specular_texture:
print(f" specular: {mat.specular_texture} level={mat.specular_level}")
if mat.reflection:
print(f" reflection: intensity={mat.reflection['intensity']}")
# Collision data
if dff.collision:
print(f"Embedded collision: {len(dff.collision.raw)} bytes")
API reference
Core classes
| Class | Description |
|---|---|
Dff |
DFF parser/writer. from_file(path), from_bytes(data), to_file(path), get_meshes(), to_generic_meshes() |
Txd |
TXD parser. from_file(path), from_bytes(data), export_to_dds(folder) |
TxdTexture |
Single texture entry. to_rgba() decodes any format to raw RGBA bytes |
Img |
IMG archive. from_file(path), read(name), find(name), extract(), extract_all(), create_v2() |
ImgEntry |
Archive entry with name, offset, size |
GenericMesh |
Flat-array mesh with *_as_bytes() helpers for format-agnostic export |
DFF data classes
| Class | Description |
|---|---|
DffGeometry |
Vertices, normals, triangles, texcoords, vertex colors, materials, and plugins |
DffMaterial |
Color, texture name, ambient/diffuse/specular, material effects, specular/reflection extensions |
DffFrame |
Name, position, rotation matrix, parent index, HAnim skeleton data |
DffAtomic |
Links a frame to a geometry |
MorphTarget |
Per-morph-target vertices and normals with bounding sphere |
BinMeshPLG |
Triangle strip/list splits by material |
SkinPLG |
Bone indices, weights, and inverse bind matrices per vertex |
HAnimPLG |
Skeleton hierarchy with bone IDs and flags |
Effect2dfxEntry |
2dfx effect (lights, particles, etc.) with position and typed data |
CollisionData |
Raw embedded COL data blob |
GenericMesh properties and methods
| Member | Description |
|---|---|
vertex_count, triangle_count |
Derived from flat array lengths |
has_normals, has_colors, has_skinning |
Quick checks for optional data |
uv_set_count |
Number of UV coordinate sets |
positions_as_bytes() |
Packed float32 LE (12 bytes/vertex) |
normals_as_bytes() |
Packed float32 LE (12 bytes/vertex) |
texcoords_as_bytes(uv_set) |
Packed float32 LE (8 bytes/vertex) |
colors_as_bytes() |
Packed uint8 RGBA (4 bytes/vertex) |
indices_as_bytes(fmt) |
"u16" or "u32" packed indices |
bone_indices_as_bytes() |
Packed uint8 (4 bytes/vertex) |
bone_weights_as_bytes() |
Packed float32 LE (16 bytes/vertex) |
Supported formats
| Game | RW Version | DFF | TXD | IMG |
|---|---|---|---|---|
| GTA III | 3.1 - 3.3 | Read/Write | Read + DDS export | v1 (Read/Write) |
| GTA Vice City | 3.4 - 3.5 | Read/Write | Read + DDS export | v1 (Read/Write) |
| GTA San Andreas | 3.6 | Read/Write | Read + DDS export | v2 (Read/Write) |
TXD supports D3D8/D3D9 platform textures: PAL4, PAL8, 16-bit (R5G6B5, A1R5G5B5, A4R4G4B4), 32-bit (A8R8G8B8, X8R8G8B8), and DXT1/DXT3/DXT5 compressed.
IMG v1 uses separate .dir + .img files. IMG v2 uses a single .img file with VER2 header.
License
MIT
Project details
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 rwfury-0.2.0.tar.gz.
File metadata
- Download URL: rwfury-0.2.0.tar.gz
- Upload date:
- Size: 27.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9631e1ad09b97bc99c76c917e779c93bfdb5d1fc8ca5abefb478f3e89956085
|
|
| MD5 |
3179e1c31d72d15e6c2d08d0eebc543e
|
|
| BLAKE2b-256 |
42dea206cf76b5c72360d3063cba787fc61bbf27427450c2b4ada870a747e31b
|
File details
Details for the file rwfury-0.2.0-py3-none-any.whl.
File metadata
- Download URL: rwfury-0.2.0-py3-none-any.whl
- Upload date:
- Size: 25.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8debc2bab18eb582e2047bd17825a92f6471229b2c754402bb66b2899bcd9cb7
|
|
| MD5 |
7bd8becd307f7d6298ba14e99def42d9
|
|
| BLAKE2b-256 |
b36baeca7e676067e5c7adf576ce3a374e44d3fe25d66351afcee14a344bb400
|