Skip to content

Asset Streaming

Work in progress

Asset streaming is under active development the supported features constantly change.

If a mod fails to load, the client log names the file and the reason. Reporting the mod that caused it in our discord bug tracker, is the fastest way to get it supported.

A bundle can replace or extend game content. It does that with files placed in an assets/ folder at the bundle root. On the client the bundle's assets are extracted and mounted, and the contents decide what happens: .archive files add or replace content, .xl files patch existing resources, and .tweak or .yaml files change TweakDB records.

Note

We aim to eventually support the full feature set that the popular modding tools ArchiveXL and TweakXL support, but currently only a partial set of features is supported

my-bundle/
└── assets/
    ├── my-mods.archive
    ├── my-patches.xl
    └── my-tweaks.yaml

Only files directly inside assets/ are picked up. Recognised extensions are .archive, .xl, .tweak, .yaml and .yml. Anything else in that folder is ignored.

Which file to use

You want to Use
Add new content: items, entities, world sectors, archives of your own .archive
Point an existing resource at your own file .xl with resource.patch
Add or modify localization strings .xl with localization
Change existing TweakDB records .tweak or .yaml
Replace an AppearanceDefinition .xl with resource.patch targeting an .app

If you are porting an existing mod, keep it in the format it already uses. A .xl from ArchiveXL and a .yaml from TweakXL are read as-is when the feature they use is supported.

Archives

Any .archive file in assets/ is mounted as a mod depot. Content inside it becomes available to the game under the resource paths it declares, which is how new entities, items and streaming blocks are added.

Packing works the same as for the base game. Build a normal WolvenKit project and put the resulting .archive in assets/. A bundle can ship several.

Resource patches

An .xl file with a resource block points a game resource at a file you ship. This is how you replace an appearance without overwriting the original archive:

assets/my-patches.xl
resource:
  patch:
    # your file, relative to assets/, then the resource it replaces
    "my_bundle\\appearances\\cool_jacket.app": "base\\characters\\garment\\jacket_01.app"

The left side is a file you ship, the right side is the game resource it replaces. Both use the game's backslash resource path syntax, and quoting them keeps YAML from treating a backslash as an escape.

What can be patched

Target Supported
.app (appearance definitions) Yes
03_night_city.devices Yes
03_night_city.psrep (persistent state) Yes
Any other resource path No, the log names the target

Targets outside those three are rejected rather than silently ignored, so a patch that does nothing is visible in the log.

World streaming

The streaming block adds streaming blocks and removes nodes from existing sectors:

assets/my-patches.xl
streaming:
  # archives whose blocks become part of the world
  blocks:
    - "my_bundle\\world\\my_blocks.streamingblock"

  # remove nodes from an existing sector
  sectors:
    - path: "base\\worlds\\03_night_city\\_compiled\\default\\03_night_city.streamingsector"
      expectedNodes: 128
      nodeDeletions:
        - index: 12
          type: worldStaticMeshNode
        - index: 47
          type: worldInstancedMeshNode

expectedNodes is a guard: the patch only applies when the sector really has that many nodes, so a game update that shifts the node order cannot silently delete the wrong thing. Always set it when you delete nodes.

Localization

The localization block adds or replaces on-screen text and subtitles:

assets/my-patches.xl
localization:
  onscreens:
    en-us:
      - "my_bundle\\localization\\onscreens.json"

  subtitles:
    en-us:
      - "my_bundle\\localization\\subtitles.json"

onscreens and subtitles take either a language keyed map, a plain list, or a single path. A plain list or single path is applied as en-us. The supported language codes are the ones the game ships: ar-ar, cz-cz, db-db, de-de, en-us, es-es, es-mx, fr-fr, hu-hu, it-it, jp-jp, kr-kr, pl-pl, pt-br, ru-ru, th-th, tr-tr, ua-ua, zh-cn, zh-tw.

Patches merge into the existing localization resources instead of replacing them, so two bundles can extend the same file.

extend, lipmaps and vomaps are parsed but not applied yet. They are logged and skipped rather than failing the bundle.

Tweaks

.tweak (RED format) and .yaml / .yml (TweakXL YAML format) files change existing TweakDB records. Both are read from assets/:

assets/my-tweaks.yaml
MyMod.MyWeapon:
  $type: Weapon_Data
  damage: 42

# Fields on an existing record are replaced by name
Items.Preset_MyWeapon:
  powerLevel: 5

Ordering

Files are applied in a fixed order so results are deterministic when several bundles touch the same record. The first character of the file name decides the tier:

First character Order
_, #, $, ! Before normal files
anything else Normal
^ After normal files

Within a tier files are applied in name order. A leading underscore is the usual way to make a patch win over a mod you are building on.

Supported directives

$type and $base are understood. These TweakXL directives are recognised but not yet implemented, and are reported rather than ignored:

Directive Purpose Status
$type Record type, same as $base Supported
$base Record type or inline inheritance Supported
$props Spread properties into a record Not yet
$instances Declare inline instances Not yet
$dlc Scope a record to a DLC Not yet
$game Scope a record to a game version Not yet

Any other $ prefixed key is rejected as unknown.

What is not supported yet

The following exist in ArchiveXL or TweakXL but have no equivalent here. A file using them loads partially or is rejected, and the log names the reason:

  • Journal patches (journal in an .xl)
  • resource.meta, resource.link and factory index blocks
  • extend, lipmaps and vomaps localization
  • The $props, $instances, $dlc and $game tweak directives
  • RED script extensions and hot reloading