Skip to main content

fs-sav

Foxhole save file parser - extracts stockpile data from .sav files.

Features

  • Parse Foxhole save files (.sav) using the uesave library
  • Extract stockpile information: items, locations, types, quantities
  • Find the game's save files (Windows, WSL, Steam Proton and Flatpak on Linux)
  • Watch files for changes with diff modes (NDJSON output)
  • Filter by hex, stockpile type, public/reserve status
  • Output compatible with foxhole-stockpiles
  • Python bindings via PyO3

Installation

Python (from PyPI)

pip install fs-sav

Rust CLI (from source)

cargo install --path .

Python (from source)

pip install maturin
maturin develop --features python

CLI Usage

Parse command

# Parse a save file
fs-sav parse path/to/War.sav

# Parse with compact output (no pretty printing)
fs-sav parse path/to/War.sav --compact

# Read from stdin
cat War.sav | fs-sav parse

Filter options

# Only public stockpiles (non-reserve)
fs-sav parse War.sav --public

# Only reserve stockpiles
fs-sav parse War.sav --reserves

# Filter by hex
fs-sav parse War.sav --hex TerminusHex

# Filter by stockpile type
fs-sav parse War.sav --type Seaport

# Only stockpiles with items
fs-sav parse War.sav --with-items

# Combine filters
fs-sav parse War.sav --hex TerminusHex --public --with-items

Watch command

# Watch for changes (outputs all stockpiles on each change)
fs-sav watch path/to/War.sav

# Watch with custom poll interval (seconds)
fs-sav watch path/to/War.sav --poll 2.0

# Only output stockpiles that changed (any field)
fs-sav watch path/to/War.sav --diff

# Only output stockpiles where items changed
fs-sav watch path/to/War.sav --diff-items

# Watch with filters
fs-sav watch path/to/War.sav --hex TerminusHex --public

Saves command

# List the map save files found on this machine (JSON, newest first)
fs-sav saves
[
  {
    "path": "C:\\Users\\me\\AppData\\Local\\Foxhole\\Saved\\SaveGames\\76561197960293553_MapData.sav",
    "steam_id": "76561197960293553",
    "modified": "2026-08-01T16:25:36Z",
    "size_bytes": 143466
  }
]

The game keeps one <SteamID64>_MapData.sav per Steam account. They are looked for in:

  • Windows: %LOCALAPPDATA%\Foxhole\Saved\SaveGames
  • WSL: the same folder of every Windows user, under /mnt/c/Users
  • Linux: the Proton prefix (steamapps/compatdata/505460/...) of every Steam library, for native Steam (~/.steam/steam, ~/.steam/root, ~/.steam/debian-installation, ~/.local/share/Steam) and Flatpak Steam (~/.var/app/com.valvesoftware.Steam/.local/share/Steam), including the extra libraries listed in libraryfolders.vdf

A save elsewhere is read by passing its path to parse or watch.

Version

fs-sav version

Python Usage

import fs_sav

# Parse a save file
stockpiles = fs_sav.parse_save("path/to/War.sav")
print(f"Found {len(stockpiles)} stockpiles")

for stockpile in stockpiles:
    print(f"  {stockpile['name']} ({stockpile['type']}): {len(stockpile['items'])} items")

# Parse with filters
seaports = fs_sav.parse_save("War.sav", stockpile_type="Seaport")
reserves = fs_sav.parse_save("War.sav", reserves=True)
terminus_public = fs_sav.parse_save("War.sav", hex="TerminusHex", public=True)

# Parse from bytes
with open("War.sav", "rb") as f:
    data = f.read()
stockpiles = fs_sav.parse_save_bytes(data)

# Track stockpiles across parses by their key
by_key = {fs_sav.stockpile_key(s): s for s in stockpiles}

# Find the save files on this machine (newest first)
saves = fs_sav.find_save_files()
if saves:
    stockpiles = fs_sav.parse_save(saves[0]["path"])

# Get parser info
info = fs_sav.info()
print(f"Implementation: {info['implementation']}, Version: {info['version']}")

Pinned tooltips that yield no stockpiles (see Skipped tooltips) are reported as UserWarnings:

import warnings

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    stockpiles = fs_sav.parse_save("War.sav")
skipped = [str(w.message) for w in caught]

Filter parameters

Parameter Type Description
public bool Only public stockpiles (non-reserve)
reserves bool Only reserve stockpiles
hex str Filter by hex name (e.g., "TerminusHex")
stockpile_type str Filter by type (e.g., "Seaport")
with_items bool Only stockpiles with items (drops empty and newly pinned ones)

Rust Library Usage

use fs_sav::{parse_save, ParseResult};

fn main() -> fs_sav::Result<()> {
    let result = parse_save("path/to/War.sav")?;
    println!("Found {} stockpiles", result.stockpiles.len());

    for stockpile in &result.stockpiles {
        println!("  {} ({:?}): {} items",
            stockpile.name,
            stockpile.stockpile_type,
            stockpile.items.len()
        );
    }
    for warning in &result.warnings {
        eprintln!("skipped: {warning}");
    }

    // From bytes: a `ParsedStockpiles` with the same stockpiles and warnings
    let parsed = fs_sav::parse_save_bytes(&std::fs::read("path/to/War.sav")?)?;
    println!("Found {} stockpiles", parsed.stockpiles.len());

    // Find the save files on this machine (newest first)
    if let Some(save) = fs_sav::find_save_files().first() {
        let result = parse_save(&save.path)?;
        println!("{}: {} stockpiles", save.path.display(), result.stockpiles.len());
    }

    Ok(())
}

Output Format

The output is a JSON array of stockpiles (Python: a list of dictionaries):

[
  {
    "name": "",
    "type": "Seaport",
    "faction": "Colonial",
    "hex": "TerminusHex",
    "coords": { "x": 0.457745, "y": 0.664469 },
    "is_reserve": false,
    "items": [
      { "code": "Rifle", "quantity": 100, "crated": false },
      { "code": "RifleAmmo", "quantity": 50, "crated": true }
    ],
    "timestamp": "2024-01-15T10:29:00Z"
  }
]

Stockpile fields

Field Type Presence Description
name str always "" for a structure's main stockpile, the reserve's name for a reserve stockpile
type str always Raw in-game CodeName (e.g. Seaport). Unknown or new types are passed through as is
faction str always Warden or Colonial: the faction whose pinned tooltips the stockpile was read from
hex str when known Hex name, e.g. TerminusHex
coords {x, y} when known Normalized map coordinates (0.0 to 1.0)
is_reserve bool always Whether this is a reserve stockpile
items list always Items in the stockpile; empty for an empty stockpile (see below)
items[].code str always Item CodeName
items[].quantity int always Quantity
items[].crated bool always Whether the item is crated
tech object bases only Garrison/upgrade build progress (0-100 per tech component)
refinery object refineries only See Refinery Data
factory_orders list factories only See Factory Orders
timestamp str or null always When the tooltip was last updated (UTC, ISO 8601); null if missing in the save, with an entry in errors
errors list of str when any Problems found reading the stockpile

Fields that are not present are omitted rather than set to null (except timestamp). Items never carry confidence, and shard, ingame_timestamp and resolution are never set: those only exist in screenshot (OCR) output.

Empty stockpiles

Empty stockpiles are returned like any other, with items: []. This includes newly pinned stockpiles, which start empty. The with_items filter drops them, so leave it off when tracking stockpiles over time.

Skipped tooltips

Only the latest details of each pinned tooltip are read. A tooltip whose latest details carry no data is skipped: the game sends none for structures the player is not on the side of, and stores empty details when it has none for the structure (e.g. it no longer exists, or the tooltip could not be matched to it). Skipped (and unreadable) tooltips are reported as warnings: in warnings of the Rust results, as Python UserWarnings, and on stderr by the CLI.

Stockpile key

Each stockpile has a key that identifies it across parses, used by fs-sav watch to track changes:

type:hex:x,y:name

Coordinates are rounded to 4 decimals (0,0 when absent), an empty type is written as Undefined and a missing hex as an empty string, e.g. Seaport:TerminusHex:0.4577,0.6645:. Build it with fs_sav.stockpile_key(stockpile) in Python or Stockpile::to_key() / fs_sav::stockpile_key() in Rust rather than reimplementing it, so it stays consistent across versions.

Stability

The fields above, the key format and the input of parse_save_bytes (the raw bytes of a .sav file) are the public contract. New optional fields may be added in any release, so ignore fields you don't use. Removing or renaming a field, changing its type or meaning, or changing the key format is a breaking change: it bumps the minor version while fs-sav is 0.x, and the major version from 1.0.

Changes between versions, breaking ones first, are listed in the CHANGELOG.

Refinery Data

The main stockpile of a refinery also carries a refinery object with its production state, as seen by the player who last opened the tooltip:

{
  "type": "Refinery",
  "items": [{ "code": "Diesel", "quantity": 2340, "crated": false }],
  "refinery": {
    "orders_access": { "level": "public" },
    "orders": [
      { "slot": 0, "code": "Cloth", "source": 0, "refined": 50, "duration": 0 }
    ],
    "storages": [
      {
        "access": { "level": "squad", "squad_id": 1 },
        "slot": 1,
        "code": "Diesel",
        "refined": 5309
      }
    ]
  }
}
  • orders_access: the queue the player had selected (level is personal, public or squad; squad_id only for squad queues).
  • orders: one entry per recipe of that queue. source is the material still waiting to be refined, refined the output ready to collect (for a squad queue, the squad's stored output) and duration the seconds left to refine the player's remaining source.
  • storages: output held per owner and recipe: the player's other squads and, while viewing a squad queue, the player's own personal output.
  • slot is the recipe's position in the refinery's recipe list (255 if not in the list); code is the item it produces, when known.
  • Counts are capped by the game at 65000 and duration at 64800 seconds.

Factory Orders

The main stockpile of a Factory or Mass Production Factory also carries the orders of the player who last opened the tooltip, one per production queue:

{
  "type": "Factory",
  "factory_orders": [
    {
      "queue": 0,
      "queue_type": "SmallArms",
      "duration": 0,
      "items": [{ "slot": 16, "code": "RifleC" }]
    },
    {
      "queue": 6,
      "queue_type": "Uniforms",
      "duration": 90,
      "items": [{ "slot": 13, "code": "ScoutUniformC" }]
    }
  ]
}
  • queue is the queue's position in the factory's queue list; queue_type names it when known. A Mass Production Factory's vehicle and structure queues are not reported in tooltips.
  • duration is the seconds until the order is done, counting the orders ahead of it in the queue (capped by the game at 64800); it is 0 for an order that is done and waiting to be collected, which is reported before pending ones.
  • items are the order's items: slot is the position in the queue's production list (255 if not in the list) and code the item, when known.

Stockpile Types

type is the structure's in-game CodeName, passed through as is, so any structure that can be pinned is supported, including ones added by game updates. Some common types:

Category Types
Bases GarrisonStation, Keep, ForwardBase1, RelicBase (1-3), FortBase (T1-T3), BorderBase, TownBase (1-3), FortGarrisonStation
Storage StorageFacility, Seaport, AircraftDepot, ConstructionYard
Production Refinery, Factory, MassProduction
Facilities Hospital, MaintenanceTunnel, FacilityFactorySmallArms, FacilityModificationCenter, FacilityTransfer (Liquid/Material/Resource), FacilityVehicleFactory (1-3)

License

MIT

Metadata

Release files for fs-sav 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fs-sav 0.4.0
File Size Uploaded Provenance
fs_sav-0.4.0.tar.gz 67.4 kB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for fs-sav 0.4.0
File
fs_sav-0.4.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
fs_sav-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
fs_sav-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
fs_sav-0.4.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
fs_sav-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 3.4 MB

Release files / fs_sav-0.4.0.tar.gz

Download URL fs_sav-0.4.0.tar.gz
Size 67.4 kB
Tags Source
SHA-256 checksum
How to use checksums
b0b17dd53304f491b07f42f4df7dcda4a4f17d2d4f2d6c87ffa5102bbd9ef526
BLAKE2b-256 checksum
How to use checksums
2f9378ae2009ab9da4171086fce4b62e6cd8cb7bf9cee1fecd466e0c31c00b8f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / fs_sav-0.4.0-cp310-abi3-win_amd64.whl

Download URL fs_sav-0.4.0-cp310-abi3-win_amd64.whl
Size 663.2 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
e83e74eae992aa11514a655201b365a476e7c5e93198a9ee3396f2022c63206a
BLAKE2b-256 checksum
How to use checksums
af45953f78a315ec4b75dfc88dcb34791c2ea057fb9e57ac8d9dd15f2850da88
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / fs_sav-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL fs_sav-0.4.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 720.7 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
ac2f5e163b711848099dccaecb705b8901630067bc26f7c5eb32b0ddee052806
BLAKE2b-256 checksum
How to use checksums
5f414041531c47586b480cbb39823ebd456ec9b8237c7b42680764ea77320b08
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / fs_sav-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL fs_sav-0.4.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 684.1 kB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
010d5a3f02fa51a6bf505c618a033154770d2ec1860785d5835a552ef211893e
BLAKE2b-256 checksum
How to use checksums
0dc51e3fd47c3d81d5f7c6cd77d4f20fdcf84cd99ed1a1c45ee12ab2194d285b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / fs_sav-0.4.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL fs_sav-0.4.0-cp310-abi3-macosx_11_0_arm64.whl
Size 624.0 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
c9c418be2d86200978a2972ecb817217034e1b6689de7803741b30258bb59a7e
BLAKE2b-256 checksum
How to use checksums
1fefede1fd1af68bca287df4b089f86adfe64e92ced6c660cbaa3d4f6332e32c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / fs_sav-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL fs_sav-0.4.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 649.0 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
f33014b7b16106ad643817246c67d4199f533a7a7e898dce25a57534ccb37c04
BLAKE2b-256 checksum
How to use checksums
7687757744431f06f0b0c21f2479c6f21086c5c2dfa97cd1693e95b6fd65b341
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Security for fs-sav 0.4.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page