vfstool
vfstool is a command-line utility for interacting with OpenMW's virtual file system (VFS). It allows users to locate files, serialize the VFS to various formats, extract files, and even collapse the VFS into a single directory for space savings, and to reuse OpenMW's feature set for other games.
Features
- Collapse the VFS: Create a set of hardlinks or symbolic links for the entire VFS in a target directory.
- Extract Files: Extract specific files from the VFS to a given directory.
- Find Files: Locate files in the VFS by name, extension, or other criteria.
- Serialize the VFS: Output the VFS structure in JSON, YAML, or TOML formats.
- Filter Remaining Files: Identify files in a directory that are replaced or not replaced by the VFS.
- Conflict and provider reports: Inspect winners, fully shadowed sources, duplicates, archives, per-source contributions, and source-to-source diffs.
- Lock/drift checks: Emit a deterministic winner manifest and compare later VFS state against it.
- Run tools against a merged VFS: Dump a merged tree, execute a child command, then capture new or modified output files.
Installation
As of version 1.0, vfstool is published in many places.
GitHub
The latest stable release can be downloaded from GitHub for macOS, Linux, and Windows here. Development builds can be found here.
AUR
yay -S vfstool-git.
crates.io
cargo install vfstool
source
Clone the repository and build the tool using cargo:
1.0 breaking API changes for Rust consumers
The vfstool CLI keeps the same user-facing archive behavior, but the library API was cleaned up
for 1.0. If you depend on vfstool_lib directly, check these before upgrading:
- VFS keys are byte-first normalized resource paths now. Use
vfstool_lib::NormalizedPathandVfsKeyInputfor VFS keys; keep usingPath/PathBuffor real host filesystem paths. These are not the same thing, despite many older APIs pretending they were. They were lying. - Bethesda archive support is provided by
dream_archive; enable it with thebeth-archivesfeature. The oldbsafeature name is gone because the feature covers both BSA and BA2. vfstool_libre-exportsserde,serde_json,serde_yaml, andtomlwhenserializeis enabled, so applications can share the library's serialization stack instead of pinning duplicate parser versions.- ZIP/PK3 support uses
zipwithout default features. Currently supported ZIP compression is stored, deflate, and LZMA; AES, bzip2, PPMd, deflate64, and zstd are intentionally not dragged into the dependency graph.
Usage
Global Options
-c, --config <CONFIG>: Path to the directory containingopenmw.cfg. If omitted, the system default location is used. For a config file with a nonstandard name, setOPENMW_CONFIGto the absolute file path instead.-r, --use-relative: Use relative paths in output.-h, --help: Describe usage of the app or any subcommand
Commands
collapse
Collapse the VFS into a target directory using hardlinks, symbolic links, or file copies.
Options:
<COLLAPSE_INTO>: Target folder to collapse the VFS into.-a, --allow-copying: Fall back to copying files if linking fails.-e, --extract-archives: Extract files from BSA/BA2 archives during collapsing.-s, --symbolic: Use symbolic links instead of hardlinks.--dry-run: Print the planned materialization actions instead of writing files.-f, --format <FORMAT>: Output format for--dry-run(json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the--dry-runplan. If omitted, results are printed to stdout.
extract
Extract a specific file from the VFS into a target directory.
Arguments:
<SOURCE_FILE>: Full relative path to a VFS file (e.g.,meshes/xbase_anim.nif).<TARGET_DIR>: Directory to extract the file to.
find-file
Locate a specific file in the VFS and return its absolute or relative path.
Arguments:
<PATH>: Full (relative) VFS path to query.
Options:
-p, --only_physical: Match only loose files; exits2if the file exists only in an archive.-s, --simple: Output the result in a simple format (no colors or formatting).
find
Search for files in the VFS using a case-insensitive regular expression matched against normalized VFS paths.
Arguments:
<PATH>: Case-insensitive regex matched against VFS paths.
Options:
-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the search results. If omitted, results are printed to stdout.
remaining
Filter the VFS to show files replacing or not replacing contents of a given directory.
Arguments:
<FILTER_PATH>: Configured data directory to filter against.
Options:
-r, --replacements-only: Show only files replacing contents of the given path.-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the filtered VFS. If omitted, results are printed to stdout.
conflicts
Report source override relationships across the load order.
Options:
-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the report. If omitted, results are printed to stdout.
shadowed
Report sources whose files are all overridden by higher-priority sources. By default this is a
summary; use -l, --list-files to include every overridden file.
Options:
-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the report. If omitted, results are printed to stdout.-l, --list-files: Include per-source overridden file lists.
Provider reports
Inspect the provider index used by the resolved VFS. These commands are projections over the same VFS provider data; they are not a second conflict system wearing a fake moustache.
Commands:
explain <PATH>: Show the winning provider and lower-priority providers for one VFS key.duplicates [PATTERN]: List VFS keys with more than one provider.PATTERN, when supplied, is a case-insensitive regex over normalized VFS keys, e.g.^meshes/ortextures/.*\\.dds$.archives: List loaded archives and how many entries currently win.archive-list <ARCHIVE>: List VFS entries supplied by one archive.contributions: Report per-source provider counts, wins, overridden files, unique files, and duplicates.validate: Report missingdata=,fallback-archive=,content=, andgroundcover=entries fromopenmw.cfg.
Options:
-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the report. If omitted, results are printed to stdout.
diff
Compare files between two configured data directories.
Arguments:
<SOURCE_A>: First configured data directory to compare.<SOURCE_B>: Second configured data directory to compare.
Options:
-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the report. If omitted, results are printed to stdout.
run
Dump the merged VFS to a directory, run a child command, then capture new or modified files to data-local or --output.
Options:
--keep-merged: Keep the merged directory after the child command exits.--output <OUTPUT>: Destination for captured files. Defaults todata-localfromopenmw.cfg.--copy: Copy files instead of hardlinking them into the merged directory.--working-dir <WORKING_DIR>: Working directory for the child process.
{} in child command arguments is replaced with the merged directory path. Deletions made by the child command are not captured.
<MERGED_DIR> must be absent or empty; run refuses to delete a non-empty directory. If the child
process starts successfully, vfstool run returns the child process exit code. Exit code 9 is
reserved for setup, spawn, or capture failures in vfstool itself.
By default, run uses hardlinks when dumping loose files into the merged directory. This avoids duplicating data, but child tools that modify files in place may modify the original loose source files through those hardlinks. Use --copy for tools that are not hardlink-safe. No, that is not a theoretical footgun. It is just how hardlinks work.
lock
Emit a deterministic lock manifest for current VFS winners.
Options:
-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the lock file. If omitted, results are printed to stdout.
drift
Compare the current VFS state to a lock manifest.
Options:
--fail-on-drift: Exit with code4when drift is detected.-f, --format <FORMAT>: Output format (json,yaml, ortoml). Default:yaml.-o, --output <OUTPUT>: Path to save the report. If omitted, results are printed to stdout.
Exit codes
vfstool uses nonzero exit codes for machine-readable failure cases:
| Code | Meaning |
|---|---|
1 |
find-file did not find the requested VFS path |
2 |
find-file --only_physical found the path only inside an archive |
4 |
drift --fail-on-drift detected drift |
5 |
validate found load-order/configuration problems |
6 |
invalid regular expression |
7 |
failed to load openmw.cfg |
8 |
invalid input, such as an unknown source path |
9 |
runtime failure while reading, writing, materializing, or starting/capturing a child command |
run passes through the child process exit code after the child starts successfully.
Examples
The published rustdoc for the vfstool binary also contains operational examples for configuration,
provider inspection, lock/drift, collapse previews, and run. The README is the quick path; rustdoc
is the self-documenting reference we try not to let rot.
Collapse the VFS into a directory with symlinks
This form is the most space-efficient variant of collapse, since it doesn't copy or extract files. It's fragile and most ideal for testing mods.
Collapse the VFS into a single directory, with extraction and hardlinks
This form consumes more space and takes longer due to extracting archive contents, but will perform better ingame and allow removing BSAs entirely.
Extract a file from the VFS
Find a file in the VFS
Search for files by extension
Show files replacing contents of a directory
Run a tool against a merged VFS
Explain why a file resolves to its winner
See which sources contribute winners or get overridden
Validate the provider index before materializing
Preview a collapse without writing files
Lock current winners and fail later if they drift
License
This project is licensed under the GNU General Public License v3.0.