Library Morrowind active
vfstool_lib
OpenMW's virtual file system as a Jess package in Luau: provider stacks, conflict and provenance reports, locks and materialization.
"org.dreamweave.vfs" = "2.0.0"- Stable
- 2.0.0 2026-10-21
- License
- MIT OR Apache-2.0
Overview
vfstool_lib is the virtual file system vfstool runs on, as a crate. Give it data directories in load order, and archives if you have them, and it builds OpenMW's VFS: one normalized key per file, later directories over earlier ones, loose files over archives. It keeps every provider of every key, not only the winner, so it can say why a file resolves the way it does, what a mod overrides, and what would change if the load order did.
local vfs = require '@vfs'
-- Two data directories, the second winning: OpenMW's order.
local stack = vfs.VFS.fromDirectories { base, patch }
assert(stack:getFile [[Textures\TX_WOOD_01.DDS]]) -- any spelling of the path
local report = stack:explain 'textures/tx_wood_01.dds'
print(report.key, #report.overridden) -- the winner, and every copy it hides
- Lookups by any spelling of a path, prefix and substring searches, and trees for display or
serialization.
- Provider reports: the chain behind one key, every duplicate, what each archive and data
directory contributes.
- Load-order analysis: conflicts, fully shadowed sources, diffs between sources, and whether
two versions of an INI, TOML or JSON file differ in what they say or only in how.
- Locks and drift: a deterministic manifest of every winner, with BLAKE3 hashes, and a report
of what changed since.
- Materialization: the VFS as one folder, with hardlinks, symbolic links, copies or extracted
archives, and a plan of what that would do.
BSA and BA2 archives are read through dream_archive,
the package org.dreamweave.dream-archive.
Documentation§
The package's reference is vfs/src/init.jsl and the modules beside it: every function is
documented where it is written, and jess luau-types prints the module's types. The guide that
explained the Rust crate lives with it on the rust branch; the Luau package's own guide is being
written. vfstool is the command line over this library.
Releases
-
2.0.0 stable
vfstool_lib is the Jess package org.dreamweave.vfs, written in Luau; the Rust crate continues on the rust branch at 1.0.0.
Breaking changes
- The library is the Jess package
org.dreamweave.vfs, module@vfs, installed with jess rather than Cargo. The Rust cratevfstool_libstays at 1.0.0 on crates.io and on this repository'srustbranch, and is no longer developed on main.
org.dreamweave.vfs-2.0.0.jpkVerify · sha256 66df02c8b706…a20018401
- Release
- 2.0.0 (stable)
- Size
- 121894 bytes
- SHA-256
- 66df02c8b706cc1a2579bb956521d4cfca970d4b9216a23028f51a6a20018401
- Commit
- 7050b6f37bd7
- Package
org.dreamweave.vfs- Content digest
- b501db857f2b619c32963763173330348ea57602f1e53303a487fa6025994fab
- Sources
- 1
Check it yourself:
sha256sum org.dreamweave.vfs-2.0.0.jpkmust print the digest above. - The library is the Jess package
-
1.0.0 stable
First stable release: the Luau binding is an l3i extension providing @dream/vfs, keys stay bytes end to end, and the top-level API is under semver.
Breaking changes
- The
luafeature makes the crate an l3i extension,lua::VfsExtension, providing the module@dream/vfs.lua::open,lua::registerandmluaare gone. - In Luau: key lists are sequence views and trees are streams (
:toTable()gives the old tables), indices are 1-based, sizes are integers, unknown option keys are errors, and paths are bytes. - Building with
luaneeds l3i's toolchain: clang, lld and cross-language thin LTO. - dream-path 1.0.
- The library writes nothing to standard output or error.
collapse_intoanddump_to_directoryreturn aMaterializationReport, the files written and each winner left out as aMaterializationSkip, instead of()and a count, andDirectoryDiffgainsunreadable, whatdiff_directory's walk could not read. In Luau,collapseIntoreturns the skipped files anddumpToDirectoryreturns them after the count.
Added
VFS::lenandVFS::is_empty, in constant time, andget_file_normalized,contains_normalizedandpaths_matching_bytesfor keys already normalized.VfsKeyInput::vfs_key_bytes, andVfsKeyInputfor[u8];VfsFile::path_bytes.- In Luau:
file:readInto(buffer, offset?),tree:count(),snapshot:len(), and a type for every function, method and view element in the plan's definitions. - File I/O in Luau:
file:size(), positionalfile:readAt(buffer, fileOffset, length?, bufferOffset?)through a memory map,file:readRange,file:readAllBuffer, andfile:open()giving aReaderwithread,readInto,seek,skip,tellandclose. - The
lua-writefeature: a write root on the VFS (writeRootoption,setWriteRoot),writeFile,openWritegiving aWriter,mkdir,removeandrename, behind thefilesystem.writeruntime capability; the root is kept canonical and refuses keys that pass through a symbolic link beneath it. - The
lua-hostfeature: ahosttable of host-path functions (readFile,readAt,writeFile,open,openWrite,stat,exists,list,mkdir,remove,rename,copy,canonicalize), behind thefilesystem.hostruntime capability. lua::VfsRequireNavigator, l3i'srequireover a VFS with.luaurcaliases and relative paths, andlua::TemplateCache, OpenMW's script template cache: one compiled template per module per VM, cloned per instance, invalidated explicitly. Thejitfeature forwards l3i's and reports each template's native code status.- Zip archives:
.jpkfiles are read as zip archives, and Zstandard entries are listed and decoded through a pure-Rust decoder.
Changed
- Looking up a key that is already normalized borrows it instead of allocating, and searches compare bytes.
- The sorted key list is built once per change to the VFS and shared by every
keys()call. lua::Vfsis shared andClone, andlua::VfsFileHandleis built withVfsFileHandle::new. dream_archive 1.0.0 and l3i 1.0.0 come from crates.io; Rust 1.92.
Fixed
- On Unix, a key that is not UTF-8 keeps its exact bytes in provider records, lock manifests and semantic conflicts, instead of a lossy spelling that looks up nothing.
collapse_intoanddump_to_directoryinto one of the VFS's own data directories leave a loose file that already is its destination where it is. They deleted it before linking or copying it.extract_fileinto the folder a loose file already lives in leaves the file there. It deleted the file, then failed to copy it.run_finalizeandrun_finalize_trackedleave a destination that already is the changed file, rewritten in place through a hard link fromoutput_dir, as it is. They copied it onto itself, which truncated it to nothing.VfsFile::file_nameandfile_stemend an archive entry's folders at\as well as/on every platform: a Morrowind BSA'smeshes\x\ex_door.nifisex_door.nifon Linux and macOS too, in trees,Displayand Luau'sfile:fileName().ConflictIndex::diff_reportcompares the keys the index holds for each source, found by exact spelling or with case and separators folded, so archives, indexes built from file lists and sources spelled in another case compare correctly; it read both paths from disk as typed. A path the index does not have is still read from disk.conflicts_reportandshadowed_reportwithoutuse_relativejoin a loose source's path with each file's own spelling, instead of the lowercase key, so every path names a file that exists.ConflictIndex::from_directoriesrecords those spellings.simulate,simulate_with_opts,simulate_impactandplan_candidate_directoryfind each key's current winner by its place in the provider stack. They compared the VFS's number for the source with the index's, which differ after a source that provides nothing, and counted unchanged keys as changed and leftcurrent_winner_sourceempty.- The layer index of a VFS keeps a source that never provided a key, such as an empty data directory, so its positions are the
source_indexof the VFS's provider records andArchiveInfo; it dropped such sources and numbered later ones lower. A source whose providers were all removed still leaves it. - A simulated reorder never lifts an archive above a loose file:
simulate,simulate_with_optsandsimulate_impactranked every source by the new order alone, so moving an archive after a data directory reported the archive winning. set_winner_fileandset_winner_loose_filerecord a new key's directories, soset_winner_file("a/b.txt", …)followed byset_winner_file("a", …)refuses the second instead of keeping a file and a directory nameda, which no VFS can write out.remove_resolved_file,remove_resolved_prefixandremove_resolved_matching_globrelease the removed keys' directories: afterremove_resolved_file("a/b.txt"),push_provider("a", …)returnedfalse.push_provider_batchchecks each entry against the ones before it, sopush_directoryandpush_archiveno longer add a file and a directory with one name;push_directorykeeps the same onefrom_directorieskeeps.DisplayforVFSanddisplay_filteredlist a file whose path has no file name, such asVfsFile::from("/"), by its whole path instead of panicking, and serialized trees list it too instead of leaving it out.DisplayforVFS,display_filteredand Luau'svfs:display()nest each directory under its parent, one│per level, and list directories that hold only directories. Every directory was printed by its last name at one level, someshes/xandtextures/xboth showed asx/, and an archive's folders looked like loose ones.- Luau's
serializewritesEntriesandProvidersviews, tree streams andVfsFilehandles, each file as its file row's fields; it refused every userdata butKeysandProviderRecordsviews, although the documentation said views serialize. collapse_intowithuse_symlinkslinks to each file's absolute path. A VFS built from relative directories made links that resolved from the output folder instead, and dangled.- In Luau, an unknown source kind in
VfsProvider.newreadsVfsProvider.new.kind: unknown source kind 'zip' (...), without a seconddream.vfs:inside it. lua::KeyBlobis exported, so a host can fill thelua::Keys(pub KeyBlob)view it could only build empty.- Every
PathBufin a report serializes as a string, with U+FFFD in place of bytes that are not UTF-8, instead of failing withpath contains invalid UTF-8 characters;diff_against_lockcompares keys and sources as that text, so a lock read back from a file does not drift.
vfstool_lib-1.0.0.crateVerify · sha256 db40ecfedfb7…12ecaeff0
- Release
- 1.0.0 (stable)
- Size
- 216746 bytes
- SHA-256
- db40ecfedfb73886364eeecf08f09124a888426636616e03d73137c12ecaeff0
- Commit
- 1a4193604e82
- Sources
- 1
Check it yourself:
sha256sum vfstool_lib-1.0.0.cratemust print the digest above. - The
-
0.10.2 stable
Fixed
VfsFile:readAll()returns a Luau string, as documented, instead of a table of bytes.
vfstool_lib-0.10.2.crateVerify · sha256 fa683d03c638…56b8aa2b2
- Release
- 0.10.2 (stable)
- Size
- 135093 bytes
- SHA-256
- fa683d03c638112193eccf6bd9aed7218e3fded4ae2fa8025dd600d56b8aa2b2
- Commit
- 7ec4c060b6a5
- Sources
- 1
Check it yourself:
sha256sum vfstool_lib-0.10.2.cratemust print the digest above.
Credits
- MaintainerDreamWeave
- The virtual file system vfstool_lib reimplementsOpenMW