A specification and reader for the Granny 1 (.grn) file format -- meshes, skeletons and animations from games that shipped on Granny 1, for which no public parser exists
  • Python 63%
  • HTML 34%
  • Shell 3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-28 11:24:03 -04:00
site Texture the JN vs JN models, and fix which UV channel is used 2026-08-28 11:17:10 -04:00
SPEC Texture the JN vs JN models, and fix which UV channel is used 2026-08-28 11:17:10 -04:00
tools Decode the payload: meshes, skeletons, skinning, materials, animation 2026-08-27 22:47:58 -04:00
.gitignore Granny 1 .grn container format, validated across 674 files 2026-08-27 19:26:57 -04:00
BRIEF.md Make the standalone parser the primary deliverable in BRIEF 2026-08-27 21:54:29 -04:00
grn.py Fix sampled-track times, render attachments double-sided, add the viewer build 2026-08-27 23:14:58 -04:00
grn2gltf.py Texture the JN vs JN models, and fix which UV channel is used 2026-08-28 11:17:10 -04:00
JNTEX-BRIEF.md Keep the JN-texture handoff brief with the others 2026-08-28 11:24:03 -04:00
PROCESS.md Decode the payload: meshes, skeletons, skinning, materials, animation 2026-08-27 22:47:58 -04:00
README.md Decode the payload: meshes, skeletons, skinning, materials, animation 2026-08-27 22:47:58 -04:00
WEBSITE-BRIEF.md Rewrite WEBSITE-BRIEF for full autonomous publication 2026-08-27 20:20:14 -04:00

grn-re — reading Granny 1 .grn files

Granny 2 (.gr2) has community parsers. Granny 1 (.grn) had none, so every game that shipped on Granny 1 had unreadable character meshes, skeletons and animations unless you ran RAD's runtime — which cannot be redistributed and is 32-bit Windows only.

Status: solved. The container, meshes, skeletons, skinning, materials and animations all read, end to end:

  • grn.py — a standalone, standard-library-only parser. Drop the one file into your project and read Granny 1 files.
  • grn2gltf.py — a converter on top of it: skinned, animated glTF 2.0 (.glb), plus OBJ for quick static inspection.
  • SPEC/GRN-V1.md — the specification every line of the parser traces to, with the corpus count behind each claim and the oracle it rests on.

Validated against 674 of 674 shipped files of SpongeBob SquarePants: Employee of the Month (AWE Games / THQ, 2002) and — the cross-title claim, now measured rather than expected — 389 of 389 files of Jimmy Neutron vs. Jimmy Negatron. The mesh layer is additionally checked corner-by-corner against a live capture of RAD's own runtime decoding the same files.

python grn.py SB_Model.grn                    what is in the file
python grn.py SB_Model.grn --skeleton         bones, parents, names
python grn2gltf.py SB_Model.grn --anim SB_walk.grn -o sb.glb
python grn2gltf.py SB_Model.grn --texture sponge=sponge.png -o sb.glb
python tools/validate.py <corpus dir>         every check, whole corpus

Textures are embedded in the files but compressed with RAD's own codec (BIKi); this project reads their headers and deliberately stops there — see PROCESS.md. The same art ships uncompressed enough to decode in the sibling .omt containers (already specified by awefan's published toolkit), which is where --texture images come from.

Corpus

No game assets are included here. You need your own copy of a game that ships .grn files.

Exporter stamp across both corpora: RAD 3D Studio MAX 4.x · 1.2b · 10-4-2000 · win32.

Scope, deliberately

This documents a file format, so that files can be read. It is not a description of RAD Game Tools' granny.dll: no function of that library is described, listed or reproduced, and none was needed — every claim in the spec came from the data files, except where §13 explicitly names the live runtime capture as the oracle. That boundary is the point, not a formality. See PROCESS.md.

Method

The habits that produced this, each of which caught a real error here:

  • Validate against the whole corpus, not one file. Every structural claim is stated with the number of files it holds for.
  • A parse that tiles a file exactly is necessary, not sufficient. The spec keeps a standing section for what is not established.
  • Test the competing reading to destruction. The record tree, the bone parents and the track-to-node mapping were each settled by eliminating alternatives, not confirming a favourite — the track mapping looked correct under the wrong reading for the one track everyone checks first.
  • Distrust tidy histograms, and scan naively on purpose.

tools/grn_dir.py, grn_tree.py, grn_names.py, grn_spans.py and grn_scan.py are the analysis instruments that got the container solved; grn.py --tree/--objects/--skeleton supersedes them for everyday use.