--- 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.
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:
.vault is read into memory, any leftover .journal is replayed on top, then the journal is deleted and started fresh..journal. The .vault is not touched.nvault_close(), clean shutdown): the .vault is truncated and rewritten in place from memory, then the journal is deleted. No temporary file, no rename.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.
Invalid vault id: -1 with run time error 10 on nvault_get or nvault_set: nvault_open() failed because the vault folder or file could not be created. Any other number means the handle was closed under the plugin. See run time error 10.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.
.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/.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..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.kill -9, the out-of-memory killer, a reboot or a panel's force stop. The bigger the vault, the wider the window. Stop servers with quit, not by killing the process.df -h whenever a vault resets.chown -R steam:steam /home/steam/hlds/cstrike/addons/amxmodx/data and never start the server as root.cp -al as in multiple servers on one VPS. A vault that existed when you made the copy stays hardlinked, because nVault rewrites it in place. Each instance saves over the other's data and deletes the other's journal. Find shared files with find /home/steam -path '*data/vault/*' -links +1, then, with the servers stopped, give each instance its own file: cp -p xpmod.vault xpmod.tmp && mv xpmod.tmp xpmod.vault.plugin_end first, the other's final saves fail with Invalid vault id. Give every plugin its own vault name..journal misses the current map. FTP in ASCII mode or a text editor mangles the binary file.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.
nvcheck.py reports OK on the live vault after a map change.find ... -links +1 check returns nothing.Invalid vault id lines..vault passes the checker.