File format specifications and standalone Python tools for modding
FINAL FANTASY XII The Zodiac Age (PC). Everything was reverse-engineered
from the game binary and checked offline against every matching file in
the game archive. Covered today: character animations (.ca, 3164 files),
character models (.cm, 1066 files) and the HD assets they depend on
(.phyre models, textures and shader effects).
Two file types, one box format. Every character is a .cm file (the
model: skeleton, mesh, textures, parameters) plus several .ca files (the
animations). Both are @FRP containers: a header, a list of offsets, then
chunks. Only the chunk set differs.
Memory aid: chunks whose name starts with @FM (M for Motion) are animation,
so they live in .ca. Everything else is the model, so .cm.
Three ways in, depending on what you want:
| I want to… | Read | Run |
|---|---|---|
| understand how FFXII animations and models work, from zero | docs/character-animations.md |
python tools/decode_ca.py c1004_00.ca --summary |
| view a character in Blender | that guide, §5 | python tools/export_ca_gltf.py c1004_00.ca -o vaan.glb |
| write my own tool or check a byte | docs/formats/ (one sheet per chunk, with a glossary) |
python tools/frp_tool.py list c1004.cm |
Every tool prints its usage with --help.
- Python 3.9+, standard library only (
pip install Pillowfor PNG output). - Game files extracted from
FFXII_TZA.vbfwith any community VBF extractor. This repo contains no game assets — bring your own legally-owned copy.
python tools/decode_ca.py c1004_00.ca --summary # what's inside an animation file?
python tools/export_ca_gltf.py c1004_00.ca -o vaan_00.glb # play it in Blender
python tools/dump_cm_mesh.py c1004.cm --glb vaan_ps2.glb # the PS2 model, skinned, textured, animated| Tool | Usage | What it does |
|---|---|---|
decode_ca.py |
<file.ca | dir> [--summary] [--curves [--motion NAME]] [--commands [--motion NAME]] [--validate-curves] [--validate-commands] [--json] |
Full .ca decoder: container, chunk list, motion names, every keyframe curve (with the constant values of the unanimated channels), and the command scripts with every opcode named. Also lists the chunks of any .cm. Validated on all 3164 game files. |
frp_tool.py list |
<file.ca | file.cm> |
Quick chunk table of any @FRP container. |
dump_cm_skeleton.py |
<file.cm> [--flat] |
Bone hierarchy (ids + parent tree). |
dump_cm_bindpose.py |
<file.cm> [--json] |
Rest-pose translation / rotation / scale of every bone. |
dump_cm_for_table.py |
<file.cm> |
The outfit table: actions, colour variants, which submesh is drawn with which texture. |
dump_cm_texanim.py |
<file.cm | dir> [--json] [--summary] |
The eye-blink / facial-patch table (strip, target, rectangle, cells, sequences). |
dump_cm_textures.py |
<file.cm> --list | --segmap [--action A] [--variant V] |
Texture descriptors and palettes, or the per-segment (texture, palette) map of an outfit. |
dump_cm_mesh.py |
<file.cm> --stats | --check | --sweep |
Decodes and validates the PS2 mesh (1045/1045 files, 0 errors). |
decode_phyre_mesh.py |
<model.dae.phyre> --info |
Header and segment dump of an HD mesh. |
decode_fx_phyre.py |
list <effect.fx.phyre> / sweep [shader_dir] |
Render-pass names and their D3D11 blend / depth / rasterizer states (what decides double-sided materials). |
| Tool | Usage | What it does |
|---|---|---|
export_ca_gltf.py |
<file.ca> [--cm <file.cm>] [-o out.glb] [--motion NAME] [--no-merge] [--fps N] |
Animations: skeleton from the sibling .cm (auto-detected) + one glTF animation per motion. Left/right locomotion halves are merged into full cycles with the game's crossfade; the secondary bones the game simulates itself are held at rest, like in game. |
dump_cm_mesh.py |
<file.cm> --glb out.glb [--lod N] [--action N] [--pc-ref <hd.glb>] [--no-mirror] [--no-skin] [--no-tex] [--no-anims] [--obj out.obj] |
The PS2 model from the .cm alone: mesh per LOD with the mirrored half rebuilt, skeleton and skinning, vertex colours, the PS2 textures of the chosen outfit embedded as PNG, animations from the sibling .ca files. --pc-ref with the model's export_glb.py output gives the exact mirror skinning and the right texture orientation on the few models TZA rewrote. |
dump_cm_textures.py |
<file.cm> --png <outdir> [--row N] |
The PS2 textures as PNG, one per (texture, palette) the model draws; --row picks a battle-status palette row. |
export_glb.py |
<model.dae.phyre> [-o out.glb] [--cm <file.cm>] [--ca <file.ca>] [--action N] [--variant N] [--weapon <w.dae.phyre>] [--shield <s.dae.phyre>] [--no-skin] [--no-anims] |
The HD model: mesh, skeleton, textures, tangents and vertex colours from the DX11 .dae.phyre, textured through the .cm outfit table, materials single- or double-sided per segment like the game. HD close-up parts supported. Validated on the whole model library. |
decode_phyre_mesh.py |
<model.dae.phyre> [-o out.obj] |
Static HD mesh to OBJ (no skinning). |
decode_dds_phyre.py |
<tex.dds.phyre> [-o out.png] [--info] |
HD texture to PNG; cube maps to DDS. |
Blender note: materials the game blends (hair, cloth with authored partial
transparency) are exported as glTF BLEND. Blender renders BLEND without
the depth pre-pass the engine uses, so overlapping geometry can look
see-through from some angles. Set those materials' Render Method to
Dithered (Material Properties > Settings) and they display like in game.
| Tool | Usage | What it does |
|---|---|---|
frp_tool.py split / pack |
split <file> -o <dir> then pack <dir> -o <file> [--verify <original>] |
Explodes a .ca / .cm into one file per chunk (+ manifest.json) and rebuilds it. Unmodified rebuilds are byte-identical (proven on all 3164 .ca + 1066 .cm). Warns about chunk combinations that crash the game. |
patch_ca_curve.py |
<file.ca> --motion NAME --bone N --channel N (--add X | --mul X) [-o out.ca] |
Shifts or amplifies one animation curve in place, without re-encoding anything. |
encode_ca.py |
roundtrip <file.ca> / rebuild <file.ca> -o out.ca |
Full .ca re-encoder (custom keyframes possible from Python). Round-trip is bit-exact on every file in the game. |
Helper modules, not meant to be run: phyre_links.py, phyre_materials.py,
phyre_reflection.py (link tables, per-segment materials and type ids of the
.phyre container, used by export_glb.py). tools/model_names.json maps
model ids to display names (c1004 → Vaan).
Use the FF12 External File Loader (the standard mod loader). Put your
rebuilt .ca or .cm at its archive path inside your mod's
mods/deploy/ff12data/ folder, e.g.
mods/deploy/ff12data/ps2data/obj_finish/in/chara/c10/c1004/c1004_00.ca.
The game never checksums these files.
Every chunk of .ca and .cm is documented with its validation scope in
docs/formats/. Reading and exporting is solved and
verified on every file in the game; editing existing animations works;
creating brand-new animations from scratch is possible from Python (keyframe
overrides in encode_ca.py) but the "edit in Blender, press a button"
workflow is not there yet. The embedded .efx effect images of gadget models
are out of scope here.
Versioning follows SemVer — see CHANGELOG.md.
Bugs and spec errors: CONTRIBUTING.md.
MIT. FINAL FANTASY XII is a trademark of Square Enix — this project is unaffiliated fan research and redistributes no game content.