npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@majesticfudgie/ifp-reader

v1.0.0

Published

A TypeScript library for parsing GTA:SA animation (`.ifp`) files - the ANP3 format used by both `anim/ped.ifp` (the default, always-loaded ped animation set) and every entry inside `anim/anim.img` (per-vehicle/per-scenario animation sets). Works in both *

Readme

ifp-reader

A TypeScript library for parsing GTA:SA animation (.ifp) files - the ANP3 format used by both anim/ped.ifp (the default, always-loaded ped animation set) and every entry inside anim/anim.img (per-vehicle/per-scenario animation sets). Works in both Node.js and the browser (uses Uint8Array input).


Installation

npm install @majesticfudgie/ifp-reader

Usage

import fs from 'fs';
import IFPReader from '@majesticfudgie/ifp-reader';

const data = new Uint8Array(fs.readFileSync('ped.ifp'));
const ifp = new IFPReader(data);

console.log(ifp.animations.length); // e.g. 294
const walk = ifp.getAnimation('WALK_player'); // case-insensitive lookup
console.log(walk?.bones.length); // one track per animated bone

Each bone track is keyed by boneId, matching the nodeId values exposed by @majesticfudgie/dff-reader's Geometry.animData (HAnim PLG) - that's the reliable way to match an animation track to a bone in a target skeleton, not boneName (casing/whitespace vary between files, e.g. "Pelvis" vs " Pelvis").

for (const bone of walk.bones) {
	console.log(bone.boneId, bone.boneName, bone.frames.length);
	for (const frame of bone.frames) {
		console.log(frame.time, frame.quaternion, frame.translation);
	}
}

Format notes

This wasn't parsed from a written spec - the byte layout below was reverse-engineered directly against anim/ped.ifp and cross-checked against every other .ifp file in the game (133 files, 1,851 animations, 46,432 bone tracks, 980,487 keyframes - zero parse errors, see this package's test history for the verification method).

Header:
  char[4]  magic            "ANP3" - the only variant seen anywhere in GTA:SA; other games/versions may
                             use a different container (e.g. ANPK) which this library does not handle.
  uint32   fileSize          informational, not required to parse
  char[24] packageName       null-terminated; bytes after the null are uninitialised garbage, not data
                             (e.g. a stray leftover "=C:=C:\3dsmax5" path from whatever export tool wrote
                             the file - ignore anything past the first null)
  uint32   numAnimations

Per animation (numAnimations times):
  char[24] name              null-terminated, same trailing-garbage caveat as packageName
  uint32   numBones
  uint32   (unknown)          total keyframe byte size across all of this animation's bones - redundant
                              with each bone's own numFrames below, not needed to parse
  uint32   (unknown)          always observed as 1

  Per bone (numBones times):
    char[24] boneName
    uint32   keyFrameType     3 = compressed rotation only, 4 = compressed rotation + translation - the
                              only two values ever observed (see verification above)
    uint32   numFrames
    uint32   boneId           matches the target DFF's HAnim node ID (Root=0, Pelvis=1, Spine=2,
                              R Clavicle=21, R UpperArm=22, L Thigh=41, R Thigh=51, ...)

    Per frame (numFrames times):
      int16 qx, qy, qz, qw     rotation quaternion, each component = raw / 4096.0
      int16 time               raw / 60.0 = seconds from clip start
      int16 tx, ty, tz         translation, each component = raw / 4096.0 - ONLY present when
                                keyFrameType is 4, absent entirely for type 3

How the scale factors were confirmed:

  • Quaternion scale (4096.0): dividing by it and taking the resulting quaternion's magnitude averaged 0.99983 (min 0.70703, max 1.00000) across every keyframe in the game (980,487 samples) - a unit quaternion is the only reason that convergence would happen at all, so this is about as confirmed as a reverse-engineered constant can get.
  • Translation scale: reuses the same 4096.0 (RenderWare's standard compression scale) - the resulting values are small, real-world-plausible per-frame deltas (centimeter scale).
  • Time scale (60.0): WALK_player's Pelvis track runs 0..72 raw across 37 frames - 72 / 60 = 1.2s, a plausible walk-cycle duration. Unlike quaternion scale, there's no hard invariant to check this against - it's the best available evidence, not a proven constant. If animation playback ever looks consistently too fast or too slow, this is the first thing to revisit.

The two "unknown" per-animation uint32 fields aren't consumed by this parser (both are derivable from data already read), but are documented here for completeness in case a future version needs them.


API

new IFPReader(data: Uint8Array)

Parses the whole file eagerly. Throws if the file isn't ANP3. Individual animations that fail to parse (e.g. an unrecognised keyFrameType) are logged via console.error and skipped, along with every animation after them in the same file (a bad read desyncs the pointer for everything that follows) - this hasn't been observed on any real game file, but mirrors the defensive fallback @majesticfudgie/col-reader uses for the same reason.

ifp.packageName: string

ifp.animations: IFPAnimation[]

ifp.getAnimation(name: string): IFPAnimation | undefined

Case-insensitive lookup - the game itself resolves animation name references the same way.


Supported Games

Currently only tested against GTA: San Andreas. GTA III/Vice City animation files may use a different (older/simpler) IFP variant - not yet investigated.