Skip to content

Virtual File System (VFS)

The Virtual File System (VFS) is the layer of 1.13 that decides which actual file on disk the game gets when it asks for something like Tilesets/0/smguns.sti. It manages all file handling and offers the game a single, unified view of the file system at runtime: every file is accessible through a path inside the VFS, independent of where it really lives on your hard disk. Real directories, SLF archives and uncompressed 7z archives can all be mapped into that view.

This page is a restructured version of VirtualFileSystem_Setup.txt (v1.1) by BirdFlu, the authoritative VFS document from the 1.13 SVN repository. If you just want to understand the Data / Data-1.13 folder layering as a player, see the configuration overview instead; if you want to package a mod of your own, start with how 1.13 modding works.

Why the VFS exists

The layout of the VFS is based on the original JA2 game data directory: the Data directory is the root of the VFS. The original SLF archives (Anims.slf, BigItems.slf, Sounds.slf, Tilesets.slf and so on) define its base directories.

Vanilla JA2 already layered these two sources:

  1. The SLF archives form the base layer.
  2. Loose files in the Data directory sit on top of that layer.

Whenever a file is accessed — say Tilesets/0/smguns.sti — the game looks in the Data directory first, and only if the file is not found there does it look in the corresponding archive (tilesets.slf in this case).

The 1.13 mod adds another layer on top: the Data-1.13 directory, which behaves the same way. When a file is accessed, the game looks in Data-1.13 first, then falls back to Data, then to the SLF archives.

The VFS generalizes this system. It externalizes the definition of these layers — called profiles in VFS jargon — into a configuration file and allows an unlimited number of them. It also adds support for uncompressed 7z archives. The order of the profiles, and which real files and directories they contain, is entirely in your hands. The price for that flexibility is that the VFS needs a correct configuration.

Glossary

These are the core VFS terms, from the bottom of the system to the top:

Term Meaning
Location A real directory or a file archive (SLF or uncompressed 7z). All files inside a location are mapped into the VFS recursively — contents of subdirectories included. You choose the path inside the VFS where they appear.
Profile A named collection of locations. Define a new profile for every mod, and keep a mod's data together: a profile can define a profile root directory that is prepended to its locations' paths during initialization. Leaving the profile root empty lets you stick together directories from very different places on your disk — but defining a separate profile for unrelated directories is the better solution.
Profile list (stack) The VFS is built from a list of profiles, processed left to right: the leftmost profile is included first, the rightmost last (on top of the stack).
Profile mode A profile is read-only by default, or read-write if declared so. You cannot write to a read-only profile.

At least one profile must be writable

Some files must be written at runtime — temporary files, savegames, logs. At least one profile must therefore be read-write, and it should normally be the top (rightmost) profile. Otherwise a read-only file in a higher profile could block access to the writable copy underneath it.

File lookup order

During file access the profile stack is processed in reverse inclusion order: a file search starts in the rightmost profile of the list and continues to the left until the file is found. In other words, later profiles override earlier ones — exactly like Data-1.13 overriding Data overriding the SLF archives in the classic setup.

The configuration file

The VFS is configured in an INI file, by default vfs_config.ini. You can point the game at a different file with the VFS_CONFIG_INI key in the [Ja2 Settings] section of Ja2.ini; if the key is absent it defaults to vfs_config.ini.

What current releases ship

Modern 1.13 releases from GitHub ship four ready-made configurations in the game folder — vfs_config.JA2113.ini (the default), vfs_config.JA2Vanilla.ini, vfs_config.UB113.ini and vfs_config.UBVanilla.ini — and select between them with commented-out VFS_CONFIG_INI lines in Ja2.ini. The shipped vfs_config.JA2113.ini is a real-world instance of exactly the profile stack described on this page: PROFILES = SlfLibs, Vanilla, v113, UserProf, with the writable UserProf profile rooted at Profiles\UserProfile_JA2113. (The shipped file also marks a few locations, such as the Intro library, with OPTIONAL = True — a key that BirdFlu's original document does not cover.)

Main section: [vfs_config]

The file starts with the main section:

[vfs_config]
PROFILES = profile1, profile2, profile3

The value of PROFILES is a list of profile names. For every name in this list a section must exist with the format [PROFILE_$name], where $name is the value from the list — for example [PROFILE_profile1].

Profile sections: [PROFILE_x]

[PROFILE_profile1]
NAME = Some Arbitrary Name (probably a mod's name)
LOCATIONS = location1, location2, location3
PROFILE_ROOT = Profiles\mod1
WRITE = TRUE
Key Required Meaning
NAME yes A human-readable name; it can be used inside the game to access the data of this profile.
LOCATIONS yes A list of location names; each needs a matching [LOC_x] section.
PROFILE_ROOT no A path prepended to all PATH values defined in this profile's location sections.
WRITE no TRUE makes the profile read-write. Default is FALSE.

At least one profile must have the WRITE property, because that is where temporary files are saved. Usually the top (rightmost) profile is the writable one.

A file can obviously only be saved into a real directory, so a writable profile is supposed to contain a location with TYPE = DIRECTORY, and that directory should be mapped as the VFS root (MOUNT_POINT = left empty). The simplest setup is a single location with these properties: all written files then end up in one place instead of being scattered over multiple unrelated directories.

Location sections: [LOC_x]

A location section name is the element from the profile's LOCATIONS list prefixed with LOC_. Every location must specify a TYPE, which is either DIRECTORY or LIBRARY:

[LOC_location1]
TYPE = DIRECTORY
PATH =
MOUNT_POINT =

[LOC_location2]
TYPE = LIBRARY
PATH = profiles/libs/archive.slf
VFS_PATH = archive.slf
MOUNT_POINT =

DIRECTORY locations take a PATH (a directory) and a MOUNT_POINT. The PATH value is appended to the profile's PROFILE_ROOT and decides where the files live in the real file system. The MOUNT_POINT value decides where they appear in the virtual file system:

real file:   PROFILE_ROOT/PATH/local_dir/file.name
maps to:     /MOUNT_POINT/local_dir/file.name

(accessible in the game simply as MOUNT_POINT/local_dir/file.name).

LIBRARY locations (archives) work the same way at initialization, but have one extra key: VFS_PATH, which is similar to PATH but names a file inside the VFS. If PATH is not defined (empty), or the file it points to cannot be opened, the VFS_PATH value is evaluated instead. For that to work, a location containing the specified file must already have been processed. This makes the configuration more complex, but it lets you integrate archives that are themselves located inside another archive.

Use cases

The three worked examples below build on each other, from a bare vanilla setup to a self-contained user mod.

Use case 1: the main game files

The main use case is setting up the base files of the game — the SLF archives as one profile, the Data directory as the profile on top of it:

[vfs_config]
PROFILES = Libs, Data

[PROFILE_Libs]
NAME = Ja2 game libraries
LOCATIONS = Anims, Faces, Tilesets
; ... in practice: one location per SLF archive ...
PROFILE_ROOT = Data

[PROFILE_Data]
NAME = Ja2 game files
LOCATIONS = Files
PROFILE_ROOT = Data

[LOC_Files]
TYPE = DIRECTORY
PATH =
MOUNT_POINT =

[LOC_Anims]
TYPE = LIBRARY
PATH = anims.slf
MOUNT_POINT =

; ... one [LOC_x] section like the above for each archive ...

[LOC_Tilesets]
TYPE = LIBRARY
PATH = tilesets.slf
MOUNT_POINT =

This reproduces the vanilla layering: the Libs profile holds the game libraries, and the Data profile with the Data directory sits on top. Because the VFS also needs a writable profile, we add a user profile that will contain only user data (the other profiles contain only game data):

[vfs_config]
PROFILES = Libs, Data, UserProfile

; [PROFILE_Libs] and [PROFILE_Data] as before

[PROFILE_UserProfile]
NAME = User files
LOCATIONS =
PROFILE_ROOT = Profiles\UserProfile
WRITE = true

If LOCATIONS is empty and WRITE is set to TRUE, a writable directory at PROFILE_ROOT is initialized automatically.

Use case 2: adding the 1.13 mod

Adding a mod is simple. Taking 1.13 itself as the example:

[vfs_config]
PROFILES = Libs, Data, v113, UserProfile

; [Libs], [Data] and [UserProfile] as before

[PROFILE_v113]
NAME = v1.13
LOCATIONS = datav113_dir
PROFILE_ROOT =

[LOC_datav113_dir]
TYPE = DIRECTORY
PATH = Data-1.13
MOUNT_POINT =

The new profile for the 1.13 mod is inserted into the profile list after the Data profile but before UserProfile. Whenever a file is accessed, it is taken from the Data-1.13 directory if it exists there; if not, the game searches the profiles below it (Data, then Libs).

Use case 3: user mods with their own write profile

Instead of only adding game data, a mod can also add or replace the writable user profile. That way you can try out a new mod (one that uses the VFS system) without risking overwriting your savegames and other temporary files:

[vfs_config]
PROFILES = Libs, Data, v113, ExperimentalMod, UserProfile_exp

; [Libs], [Data] and [v113] as before

[PROFILE_ExperimentalMod]
NAME = Files for experimental Mod
LOCATIONS = ExpMod_Libs, ExpMod_Files
PROFILE_ROOT = Profiles/ExpMod

[LOC_ExpMod_Libs]
TYPE = LIBRARY
PATH = exp_mod.7z
MOUNT_POINT = Tabledata

[LOC_ExpMod_Files]
TYPE = DIRECTORY
PATH = ModFiles
MOUNT_POINT = Interface

[PROFILE_UserProfile_exp]
NAME = User file for ExpMod
LOCATIONS =
PROFILE_ROOT = Profiles\User_ExpMod
WRITE = true

Note

The user profile directory has to exist, even if it is empty.

Because the configuration filename is defined in Ja2.ini, you can keep several VFS configuration files side by side and switch between them easily:

[Ja2 Settings]
;VFS_CONFIG_INI = vfs_config.main.ini
;VFS_CONFIG_INI = vfs_config.my_mod.ini
VFS_CONFIG_INI = vfs_config.ExpMod.ini

This is exactly how current releases let you switch between JA2 1.13, vanilla JA2, and the Unfinished Business variants — one shipped vfs_config.*.ini per game mode, with the inactive lines commented out in Ja2.ini.

The += extension: composing config files

In a VFS-related INI file you can use the += operator, which appends a value to a previously set entry, essentially turning it into a list. This works because internally a data structure (PropertyContainer) is used that contains a set of mappings, each mapping two keys to a property:

[key1][key2] = property

When initialized from an INI file, key1 is a section, key2 a key, and property the value of that section–key pair. The same structure can also be initialized from multiple INI files, where += appends a new value to an already defined entry.

The VFS uses this so that VFS_CONFIG_INI in Ja2.ini accepts a list of configuration files. Since a single value is just a one-element list, the extension fits into the system without requiring changes to existing configuration files.

The practical application is the stand-alone configuration of mods: each mod brings its own configuration file, which is merged with the configuration files of other mods. That makes it easy to build a valid configuration for a large number of mods — otherwise a separate configuration file would be needed for every mod combination, and even with a small number of combinable mods the total number of files would grow very fast.

For the default combination (vanilla + 1.13) there would be two mod configuration files and one user configuration file:

; ------------- vfs_config.ja2.ini
[vfs_config]
PROFILES = SlfLibs, Ja2Data

[PROFILE_SlfLibs]
; ...

[PROFILE_Ja2Data]
; ...

; [LOC_...] sections ...
; ------------- vfs_config.v113.ini
[vfs_config]
PROFILES += v113

[PROFILE_v113]
NAME = v1.13
LOCATIONS = datav113_dir
PROFILE_ROOT =

[LOC_datav113_dir]
TYPE = DIRECTORY
PATH = Data-1.13
MOUNT_POINT =
; ------------- vfs_config.user.ini
[vfs_config]
PROFILES += UserProf

[PROFILE_UserProf]
NAME = User Profile
LOCATIONS =
PROFILE_ROOT = Profiles\UserProfile
WRITE = true

The actual combination of mods is then done in Ja2.ini. This is the vanilla game:

[Ja2 Settings]
VFS_CONFIG_INI = vfs_config.ja2.ini, vfs_config.user.ini

and this is the setup for the 1.13 mod:

[Ja2 Settings]
VFS_CONFIG_INI = vfs_config.ja2.ini, vfs_config.v113.ini, vfs_config.user.ini

Other mods are added by simply inserting their configuration file into the list:

[Ja2 Settings]
VFS_CONFIG_INI = vfs_config.ja2.ini, vfs_config.v113.ini, mod1.ini, mod2.ini, vfs_config.user.ini

One user profile per mod combination

To avoid mixing savegames and temporary files of different mod combinations, use a separate user profile directory for each: define every user profile in its own user configuration file, and select it by putting that file at the end of the VFS_CONFIG_INI list.

Credits

The VFS and the original setup guide this page is based on were written by BirdFlu. The original document is VirtualFileSystem_Setup.txt (v1.1) in the 1.13 SVN repository. For what the acronyms on this page mean elsewhere in the community, see the glossary.

Sources