--- title: "Fix: nVault Corrupted or Reset (AMX Mod X Vault Data Lost)" excerpt: "Find out why an AMX Mod X nVault file lost its data, check the file, recover it without making things worse, and stop it happening again with backups, pruning and SQLite." category: "errors" tags: "nvault, amxx, vault, data loss, backups, sqlite" image: "" read_time: "7 min read" ---

nVault is the AMX Mod X module behind most plugins that save XP, money or settings between maps. Each vault is one binary file of key/value pairs, usually keyed by SteamID. The format has no checksum, and the module never reports a file that failed to load: it starts with whatever it managed to read and writes that back over the file at the end of the map. That is why a damaged vault so often becomes permanent data loss, and why step one of any recovery is to copy the file before the map changes.

1. Where vault files live and when they are written

cstrike/addons/amxmodx/data/vault/<name>.vault
cstrike/addons/amxmodx/data/vault/<name>.journal

<name> is the string the plugin passes to nvault_open(); search the plugin's .sma for it if the file name is not obvious. If you changed amxx_datadir in configs/core.ini, the folder follows it. Windows uses the same layout. The lifecycle, from the module source:

So a crash in the middle of a map is usually survivable: the journal is replayed at the next start, minus the last few writes still sitting in a buffer. The dangerous moment is the rewrite at map end.

2. Match the symptom

3. Check the file

A healthy vault starts with the four bytes TLVn. A zero-byte file is normal only for a vault created during the current map.

head -c 4 xpmod.vault; echo

For a full check, save this as nvcheck.py and run python3 nvcheck.py xpmod.vault against a copy. Any machine with Python 3 will do, including a Windows PC.

import struct, sys
data = open(sys.argv[1], "rb").read()
if len(data) < 10 or data[:4] != b"TLVn":
    sys.exit("BAD: no nVault header (file must start with TLVn)")
version, entries = struct.unpack_from("<Hi", data, 4)
pos, good = 10, 0
while good < entries and pos + 7 <= len(data):
    stamp, klen, vlen = struct.unpack_from("<iBH", data, pos)
    if pos + 7 + klen + vlen > len(data):
        break
    pos += 7 + klen + vlen
    good += 1
print("header says %d entries, %d readable" % (entries, good))
if version != 0x0200 or good != entries or pos != len(data):
    sys.exit("BAD: damaged after entry %d" % good)
print("OK")

It walks the file the way the module does. Anything but OK is a damaged file. OK with everyone reset means the keys changed, not the file: a new Reunion salt re-rolls every non-Steam SteamID.

4. Recover

  1. Copy the vault and journal now, before the map changes. Mid-map the .vault is not being written, so the copy is safe. At the next map change or clean shutdown, the module overwrites it with whatever it holds in memory.
    mkdir -p ~/vault-rescue
    cd /home/steam/hlds/cstrike/addons/amxmodx/data/vault
    cp -p xpmod.* ~/vault-rescue/
  2. Stop the server. Files restored while it runs are overwritten at the next map change.
  3. Restore from a backup if you have one. Copy the backup's .vault into place and delete the current .journal. That journal holds this session's writes, including the zeroed values the plugin saved for players after the reset; replaying it onto the backup would wipe them again.
  4. Without a backup, salvage or accept the loss. If the checker shows most entries readable (a truncated file), start the server with it: the module keeps every complete entry and writes a clean file at map end. If the header is gone, rename both files to .bad and let the plugin start a fresh vault. Viewers listed on the AlliedModders wiki (nVault Reader, nVault Editor) can show a readable file's contents; nothing rebuilds a missing header.
  5. Fix the cause from the list below before starting again, then watch one full map change.

Common causes

5. Prevention

Back up the vault folder every few hours with dated archives, journals included, and keep many generations so one copy taken mid-save does not matter:

#!/bin/bash
SRC=/home/steam/hlds/cstrike/addons/amxmodx/data/vault
DST=/home/steam/backups/vault
mkdir -p "$DST"
tar czf "$DST/vault-$(date +%F_%H%M).tar.gz" -C "$SRC" .
ls -1t "$DST"/vault-*.tar.gz | tail -n +57 | xargs -r rm --

Run it from the steam user's crontab with 0 */6 * * *; 56 archives is two weeks. Getting copies off the box is covered in backing up configs, bans and stats.

Prune in code if you maintain the plugin. Entries saved with nvault_set() carry a timestamp, so dropping players not seen for 30 days is one call after opening:

public plugin_cfg()
{
    g_vault = nvault_open("xpmod")
    if (g_vault == INVALID_HANDLE)
        set_fail_state("Cannot open vault xpmod")

    nvault_prune(g_vault, 0, get_systime() - 30 * 86400)
}

Entries written with nvault_pset() have no timestamp and are never pruned, so do not use it for per-player data. The set_fail_state() line turns a silent failure into a visible one.

Move structured data to SQLite. Anything with several fields per player, or that you want to sort into a top 15, belongs in a real database: see the SQLite module. nVault has no native that lists its keys, so migrate lazily: when a player connects and is missing from SQLite, read their old value with nvault_lookup(), insert it, then nvault_remove() the key.

Verification checklist