{"id":44593883,"url":"https://github.com/dend/filmshell","last_synced_at":"2026-02-16T09:01:36.426Z","repository":{"id":337757749,"uuid":"1153842942","full_name":"dend/filmshell","owner":"dend","description":"🎥 CLI to help you analyze Halo Infinite film files","archived":false,"fork":false,"pushed_at":"2026-02-12T03:46:10.000Z","size":3855,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-14T13:40:04.315Z","etag":null,"topics":["api","halo","halo-api","halo-infinite","reverse-engineering"],"latest_commit_sha":null,"homepage":"https://gruntapi.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dend.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-09T18:22:12.000Z","updated_at":"2026-02-12T03:46:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dend/filmshell","commit_stats":null,"previous_names":["dend/filmshell"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dend/filmshell","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dend%2Ffilmshell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dend%2Ffilmshell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dend%2Ffilmshell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dend%2Ffilmshell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dend","download_url":"https://codeload.github.com/dend/filmshell/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dend%2Ffilmshell/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29473357,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T06:58:05.414Z","status":"ssl_error","status_checked_at":"2026-02-15T06:58:05.085Z","response_time":118,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","halo","halo-api","halo-infinite","reverse-engineering"],"created_at":"2026-02-14T07:34:59.965Z","updated_at":"2026-02-15T08:00:44.609Z","avatar_url":"https://github.com/dend.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"media/filmshell-logo.png\" alt=\"filmshell logo\" width=\"200\" height=\"200\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eFilmShell\u003c/h1\u003e\n\u003cp align=\"center\"\u003e🎥 CLI to help you analyze Halo Infinite film files\u003c/p\u003e\n\n\u003e [!WARNING]\n\u003e This is **not** an official Microsoft or Halo Studios product. FilmShell relies on undocumented APIs that may change or be restricted at any time. Use at your own risk. So far there have been no negative consequences from using these APIs, but Microsoft or Halo Studios may choose to change that stance at any point, which could result in account restrictions or bans.\n\n\u003e [!NOTE]\n\u003e This project is experimental and exploratory. The film binary format is not publicly documented and motion extraction relies on reverse-engineered heuristics. Many films may not produce correct paths yet as work continues on more universal parsing.\n\nFilmShell connects to the Halo Infinite API, downloads theater film data, and turns it into something you can actually see. It pulls per-player movement out of the raw binary chunks, grabs map metadata (MVAR) for object placements, and renders SVG visualizations of player paths overlaid on the map geometry. For background on the early exploration that led to this project, see [Extracting Stats From Film Files in Halo Infinite](https://den.dev/blog/extracting-stats-film-files-halo-infinite/).\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"media/filmshell.gif\" alt=\"FilmShell CLI demo\"\u003e\n\u003c/p\u003e\n\n## Table of contents\n\n- [Prerequisites](#prerequisites)\n- [Creating the Entra application](#creating-the-entra-application)\n- [Setup](#setup)\n- [Usage](#usage)\n- [Output](#output)\n- [Object IDs](#object-ids)\n- [Reference films](#reference-films)\n- [How it works](#how-it-works)\n- [Binary viewer](#binary-viewer)\n- [License](#license)\n- [Acknowledgements](#acknowledgements)\n\n## Prerequisites\n\n- [Node.js](https://nodejs.org/) 18 or later\n- A Microsoft Entra application registered for Xbox Live authentication ([see below](#creating-the-entra-application))\n\n## Creating the Entra application\n\nFilmShell authenticates through Xbox Live using a personal Microsoft account. You need to register an application in the Azure portal to get a client ID.\n\n1. Go to the [Azure portal](https://portal.azure.com/) and navigate to **Microsoft Entra ID** \u003e **App registrations** \u003e **New registration**.\n2. Give the application a name (e.g., \"FilmShell\").\n3. Under **Supported account types**, select **Personal Microsoft accounts only**.\n4. Under **Redirect URI**, select the **Mobile and desktop applications** platform and enter `http://localhost/callback`.\n5. Click **Register**. Copy the **Application (client) ID** from the overview page - this is your client ID.\n\n\u003e [!NOTE]\n\u003e A [public client application](https://learn.microsoft.com/entra/identity-platform/msal-client-applications) is sufficient. You do not need to create a client secret.\n\nNo additional API permissions need to be configured. The Xbox Live scopes (`Xboxlive.signin` and `Xboxlive.offline_access`) are requested automatically at runtime.\n\n## Setup\n\n1. Clone the repository and install dependencies:\n\n   ```sh\n   git clone https://github.com/dend/filmshell.git\n   cd filmshell\n   npm install\n   ```\n\n2. Copy the example config and add your client ID:\n\n   ```sh\n   cp config.example.json config.json\n   ```\n\n   Edit `config.json` and replace `YOUR_ENTRA_CLIENT_ID_HERE` with the Application (client) ID from [the step above](#creating-the-entra-application).\n\n3. Build the project:\n\n   ```sh\n   npm run build\n   ```\n\n\u003e [!WARNING]\n\u003e `tokens.bin` is encrypted at rest using a key derived from your machine's hostname and OS username. This is **not** a substitute for proper secret management - anyone with access to the same machine account can derive the same key. If your hostname or username changes, the existing tokens cannot be decrypted and you will need to re-authenticate.\n\n## Usage\n\n### Download and process your latest match\n\n```sh\nnpm start\n```\n\nThis will:\n\n1. Open a browser-based Microsoft sign-in flow\n2. Fetch your most recent match from the Halo Infinite API\n3. Download and decompress the film chunks\n4. Fetch the map's MVAR asset and extract object placements (spawn points, weapons, equipment)\n5. Extract player movement from the film binary data\n6. Generate an SVG visualization in the film's output directory\n\n### Process multiple recent matches\n\n```sh\nnpm start -- --count 5\n```\n\n### Re-process an already-downloaded film\n\nIf you've previously downloaded a film, you can re-process it without authenticating:\n\n```sh\nnpm start -- --match-id \u003cmatch-guid\u003e\n```\n\nThe match GUID corresponds to a directory under `films/`.\n\n### Development mode\n\nBuild and run in one step:\n\n```sh\nnpm run dev\n```\n\n## Output\n\nEach processed match creates a directory under `films/\u003cmatch-id\u003e/` containing:\n\n| File | Description |\n|---|---|\n| `match-metadata.json` | Match stats from the Halo API |\n| `film-metadata.json` | Film asset metadata (chunks, duration) |\n| `filmChunkN_dec` | Decompressed film chunk binaries |\n| `mvar.json` | Parsed map variant Bond document (if MVAR was fetched) |\n| `objects.json` | Extracted map objects with world positions |\n| `path.svg` | Player movement path visualization |\n\n## Object IDs\n\nFilmShell uses `src/objects.json` to map numeric object IDs in MVAR data to human-readable names (e.g., spawn points, weapons, flags). The Halo Infinite binary formats don't include object names directly - only integer references.\n\n\u003e [!WARNING]\n\u003e The object ID list is incomplete and may require changes as new objects are discovered. Contributions to expand the list are welcome!\n\nThe most reliable way to discover new object IDs is to create custom maps in Forge with known object placements, then perform MVAR dumps of those maps to correlate the IDs with the objects you placed. This lets you build up the mapping incrementally as you encounter new object types.\n\n## Reference films\n\nThe repository includes eight pre-downloaded films in `films/` that can be used to validate the implementation without needing API access. All matches were played on [Aquarius](https://www.halowaypoint.com/halo-infinite/ugc/maps/33c0766c-ef15-48f8-b298-34aba5bff3b4). The first six have the human player making a full loop of the map; the seventh is a solo combat test; the eighth is a PvE bot fight. Re-process any of them with:\n\n```sh\nnpm start -- --match-id \u003cmatch-guid\u003e\n```\n\n| Match ID | Players | Type | Notes |\n|---|---|---|---|\n| `53a98da9-718d-4374-b739-b0ee2e7033ba` | 2 humans | PvP | Full loop |\n| `a422938f-dd2e-4c9d-88a5-fab76e6c9efa` | 2 humans | PvP | Full loop |\n| `4bfdd8b9-0a51-4646-a25c-4e28c2b2f8a1` | 1 human + 1 bot | PvE | Full loop |\n| `b632adeb-1756-4c7b-b230-f2fd95d9b85b` | 1 human + 1 bot | PvE | Full loop |\n| `152dd30f-a99b-4e51-addb-7679c566a725` | 1 human | Solo | Full loop |\n| `2cf8d130-4363-48b1-b7b8-62b5a6e01454` | 1 human | Solo | Full loop |\n| `b49f075b-f82b-4ad6-940b-fc31f53756bb` | 1 human | Solo | Half-circle, MA40 AR fire at south spawn, MK50 Sidekick fire at north spawn |\n| `3f5b80c8-c5f2-4f3f-a3b9-ff286100866e` | 1 human + 1 bot | PvE | Bot fight, human engages bot in combat |\n\nEach film directory contains the decompressed film chunks, match/film metadata, cached map objects, the parsed MVAR document, and the generated SVG path visualization.\n\n\u003e [!NOTE]\n\u003e **Films from matches with bots currently produce incorrect path output**. The root cause is under investigation.\n\n## How it works\n\n1. **Authentication** - OAuth flow through Microsoft Entra using [conch](https://www.npmjs.com/package/@dendotdev/conch) to obtain Xbox Live tokens, then [grunt](https://www.npmjs.com/package/@dendotdev/grunt) to exchange them for a Spartan token for the Halo Infinite API.\n2. **Film download** - Uses grunt's `HaloInfiniteClient` to fetch match history, retrieve the film asset, and download/decompress zlib-compressed film chunks.\n3. **Map metadata** - Fetches the map variant (MVAR) asset, parses the Bond Compact Binary v2 format, and extracts object placements (spawn points, weapons, objectives) with world coordinates.\n4. **Motion extraction** - Scans film chunk binaries for frame markers (`A0 7B 42`), auto-detects the position encoding variant per player, and accumulates coordinate deltas into movement paths. Supports multiple encoding formats across different maps.\n5. **SVG generation** - Scales motion data to world coordinates using map bounds, anchors paths to detected spawn positions, and renders per-player movement trails with map object overlays.\n\n## Weapon fire events\n\n\u003e [!NOTE]\n\u003e Fire event decoding is not yet implemented in FilmShell. The findings below document the binary encoding for future implementation. Initial analysis and weapon ID discovery by [Andy Curtis](https://github.com/acurtis166) — see the full [discussion thread](https://github.com/dend/blog-comments/issues/5).\n\nWeapon fire events are encoded in the film chunk bit stream at a **4-bit offset** from byte boundaries. Each fire event contains the weapon ID, weapon slot, a rolling fire counter, and an aim vector using [octahedral encoding](https://stackoverflow.com/a/74745666).\n\n### Fire event structure\n\nAll fields are bit-packed at a 4-bit shift:\n\n| Offset | Size | Field | Notes |\n|---|---|---|---|\n| 0 | 1 byte | Lead byte | `0x0d` for fire events |\n| 1 | 1 byte | Player/constant | `0x26` — bit-packed with player index |\n| 2 | 1 byte | Constant | `0x00` |\n| 3 | 1 byte | Constant | `0x40` (low 2 bits may vary) |\n| 4 | 1 byte | Fire counter | Increments by 4 per shot, wraps at 256 |\n| 5 | 1 byte | Weapon slot | `0x01` = primary, `0x03` = secondary |\n| 6 | 8 bytes | Weapon ID | See table below |\n| 14 | 1 byte | Aim octant | 0–7, selects face in octahedral projection |\n| 15 | 2 bytes | Aim vector | uint16 encoding position within octant face |\n| 17 | 2+ bytes | Aim data | Additional aim vector components |\n\nBecause of the 4-bit shift, weapon IDs don't appear as literal byte sequences in the file. To search, compute the shifted 7-byte pattern:\n\n```\npattern[k] = ((weaponId[k] \u003c\u003c 4) | (weaponId[k+1] \u003e\u003e 4)) \u0026 0xFF,  for k = 0..6\n```\n\n### Known weapon IDs\n\nDiscovered by [Andy Curtis](https://github.com/acurtis166). Weapon variants (e.g., S7 Flexfire) share the same ID as the base weapon. Most IDs share the `42 c9 67 9f` suffix.\n\n| Weapon | ID |\n|---|---|\n| Bandit Evo | `6a cd c4 4d 42 c9 67 9f` |\n| BR75 | `2b 18 24 d5 42 c9 67 9f` |\n| Cindershot | `23 04 47 b1 42 c9 67 9f` |\n| CQS48 Bulldog | `b6 19 d8 4a 42 c9 67 9f` |\n| Disruptor | `84 bd 29 ed 42 c9 67 9f` |\n| Heatwave | `2a c9 c2 ff 42 c9 67 9f` |\n| M392 Bandit | `2f b2 1c 87 42 c9 67 9f` |\n| M41 SPNKr | `71 ab 0a 2c 42 c9 67 9f` |\n| MA40 AR | `48 c1 9d 2d 42 c9 67 9f` |\n| MA5K Avenger | `f5 c3 35 df e7 23 2c 0b` |\n| Mangler | `80 97 7b a5 42 c9 67 9f` |\n| Mk51 Sidekick | `f4 08 19 0f 42 c9 67 9f` |\n| MLRS-2 Hydra | `76 7d b9 6d 42 c9 67 9f` |\n| Needler | `b5 33 95 7e 42 c9 67 9f` |\n| Plasma Pistol | `c3 54 29 46 42 c9 67 9f` |\n| Pulse Carbine | `30 48 4e a6 42 c9 67 9f` |\n| Ravager | `c3 0d 87 c7 42 c9 67 9f` |\n| S7 Sniper | `0a 19 92 bc 42 c9 67 9f` |\n| Shock Rifle | `93 87 a8 b9 42 c9 67 9f` |\n| Skewer | `0d 20 c4 69 42 c9 67 9f` |\n| Stalker Rifle | `da f1 93 c7 42 c9 67 9f` |\n| VK78 Commando | `fd 98 55 4c 42 c9 67 9f` |\n| Vestige Carbine | `3e 07 02 17 42 c9 67 9f` |\n\n\u003e [!NOTE]\n\u003e Andy's research indicates that automatic weapons (like the MA40 AR) may have some shots dropped in the film sampling — the fire counter can skip values, and the shot count may not match the ammo consumed. Semi-automatic and single-shot weapons appear to record all fire events consistently.\n\n## Binary viewer\n\nThe repository includes a browser-based binary viewer for inspecting raw film data. It is intended as a reverse-engineering tool to help map frame fields and discover new patterns.\n\n```sh\nnpm run viewer        # launch dev server\nnpm run viewer:build  # production build to dist/viewer/\n```\n\nThe viewer provides:\n- A **hex dump** with color-coded bytes by field type (marker, tick, frame type, format byte, position data, state data, extended data)\n- A **frame table** with filtering by player, base type, subtype, and d0 high nibble\n- **Bidirectional linking** — click a frame in either view to highlight it in both\n- A **chunk map** showing relative chunk sizes with click-to-navigate\n- Frame boundary markers in the hex view\n\n## License\n\n[MIT](LICENSE)\n\n## Acknowledgements\n\n- Logo: \u003ca href=\"https://www.flaticon.com/free-icons/video-camera\" title=\"video camera icons\"\u003eVideo camera icons created by siens - Flaticon\u003c/a\u003e\n- Object ID mapping from [artificeslab/mvar_decoder](https://github.com/artificeslab/mvar_decoder/blob/main/object_IDs.json)\n- MVAR format insights from [soupstream/InfiniteVariantTool](https://github.com/soupstream/InfiniteVariantTool)\n- General tag details from [Gamergotten/Infinite-runtime-tagviewer](https://github.com/Gamergotten/Infinite-runtime-tagviewer)\n- Halo Infinite asset parsing reference from [Surasia/libpyinfinite](https://github.com/Surasia/libpyinfinite)\n- [Andy Curtis](https://github.com/acurtis166) for [film file exploration](https://github.com/dend/blog-comments/issues/5), weapon fire event decoding, weapon ID discovery, and octahedral aim vector analysis\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdend%2Ffilmshell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdend%2Ffilmshell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdend%2Ffilmshell/lists"}