Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What is RPFM?

Rusted PackFile Manager (RPFM) is a modding tool for every Total War game from Empire (2009) onwards. It’s a Rust + Qt6 reimplementation of the original PackFile Manager (PFM), built to be fast, accurate and pleasant to spend hours in.

What it does

In modern Total War games, the data the engine utilizes lives inside .pack files: archives that bundle DB tables, localisation strings, scripts, models, textures, animations, sounds and more. Modding the game means producing your own Pack and telling the launcher to load it on top of the vanilla data.

RPFM is the editor where that Pack is built. It opens, inspects, edits and saves Packs, and ships editors/viewers for every common file type inside them:

  • DB tables — schema-aware grids with lookups, references, sorting, filtering, spreadsheet copy/paste and TSV import/export.
  • Loc files — localisation tables with TSV import/export.
  • Text & scripts — Lua, XML, JSON, HLSL and dozens of other text formats with KDE syntax highlighting.
  • Animations — AnimPack, AnimTable, AnimFragment, MatchedCombat.
  • Models & visuals — RigidModel, DDS/atlas images, Portrait Settings.
  • Video — the proprietary CA_VP8 format.
  • Specialised — ESF (saves and startpos), BMD (battle maps), and a few more.

Beyond editing individual files, RPFM understands a Pack as a whole and how it relates to the rest of the game data. That’s where the more interesting features live:

  • Diagnostics that catch invalid references, missing locs, broken portrait variants, animation gaps and dozens of other classes of mod bugs before the game does.
  • Global Search with regex across the open Pack — or across vanilla and parent mods too.
  • Dependency manager that keeps track of vanilla data and parent mods and powers reference lookups everywhere else.
  • Pack optimiser that strips ITM rows, datacore deletes and unused content to keep the final Pack lean.
  • MyMod workspaces that bundle a Pack with its assets and templates, with one-click install to the game folder.
  • Translator that makes keeping mod translations up to date painless.

How RPFM is built

RPFM is split into two cooperating processes:

  • rpfm_ui — the Qt6 desktop application you interact with. Everything you see — menus, editors, dialogs — lives here.
  • rpfm_server — a headless backend that does the heavy work: file I/O, schema decoding, diagnostics, search, dependency resolution. The UI launches it automatically.

This split exists because the same backend is also exposed over WebSocket and the Model Context Protocol, so AI tools and other clients can drive Total War file editing programmatically. If you’re a tool author, see the Server section.

A note on PFM

If you’ve used the original PackFile Manager, the menus and layouts will feel familiar — that’s deliberate. RPFM started as a straight reimplementation of PFM in Rust and Qt and has grown well past it, but the muscle memory should still mostly transfer.

Where to go next

You can always reach this manual from inside RPFM via About → Open RPFM Manual, or by clicking in the manual button in the welcome page.

Installation

RPFM ships pre-built for Windows and Linux. macOS isn’t supported yet — there’s no maintained build.

Windows

  1. Download the latest rpfm-vX.Y.Z-x86_64-pc-windows-msvc.zip from the releases page.
  2. Extract the archive anywhere — your Documents folder, a tools folder on a secondary drive, anywhere you like. RPFM is portable; it doesn’t need an installer.
  3. Run rpfm_ui.exe.

That’s it. The first launch will take you to first-time configuration.

Heads-up: Don’t extract the zip into a path that requires admin rights (e.g. Program Files). RPFM writes certain update-related files relative to where its files are, and a write-protected install path causes confusing errors.

If RPFM says rpfm_server Not Responding: RPFM’s backend, rpfm_server.exe, took too long to start. This usually means Windows security or your antivirus blocked it, as it’s not signed. Allow rpfm_server.exe (or RPFM’s whole folder) in your security software, then restart RPFM.

Linux

Arch Linux and derivatives

The recommended install is the rpfm-bin package on the AUR:

# With your favourite AUR helper
paru -S rpfm-bin
# or yay -S rpfm-bin

There’s also rpfm-git if you want to build from the latest develop branch yourself.

Other distributions (Flatpak)

A Flatpak is the easiest way to run RPFM on any distro that supports Flatpak. The Flatpak bundles Qt6 and the KDE Frameworks RPFM needs, so you don’t have to install them yourself. Refer to the project’s releases page for the current Flatpak download.

Paths inside other Flatpak apps: the Flatpak can’t read the data folders of other Flatpak apps (for example, a game installed through Steam’s Flatpak), even if the file dialog lets you pick them. When one of your configured paths is in one, RPFM shows the command to grant it access (Copy Command). Run it in a terminal with RPFM closed, or set the same paths with Flatseal, then start RPFM again.

Building from source

If your distro doesn’t have a maintained package and the Flatpak doesn’t fit, see Building from source.

macOS

There’s no maintained macOS build. The qt_* Qt6 bindings RPFM uses build on macOS in principle, but nobody is currently producing or testing macOS releases. If you want to take that on, contributions are welcome.

Updating

By default RPFM checks for updates on launch and shows a dialog when one is available. The update flow downloads the new version, replaces the binaries and restarts. Beta and stable channels are configurable from Preferences → Updates.

On Linux, in-app updates are disabled — your distribution’s package manager (or Flatpak) is the one in charge of updates. Update through the same channel you used to install.

Verifying it works

After launching for the first time you should see the welcome page, with quick links to the manual, recent Packs, and your update status. If you got that far, the install is good. Move on to first-time configuration.

First-time configuration

RPFM works on Pack files, but most of the interesting features — diagnostics, dependency lookups, MyMod, the optimiser — only work properly when RPFM knows where your games live and which schemas to use. The first-time configuration takes about a minute and unlocks everything.

Open Preferences (Edit → Preferences, or Ctrl+P then “Preferences”). Settings are laid out as a single scrollable list grouped into sections — Paths, General, Table, Debug, Diagnostics, Telemetry, AI — with a search field at the top you can use to jump straight to a setting by name.

1. Set the game paths

In the Paths section, point RPFM at every Total War game install you care about. For each game, browse to the folder that contains the game’s executable (e.g. …/steamapps/common/Warhammer 3 for Warhammer 3).

On first launch RPFM scans your Steam libraries and fills in the game (and Assembly Kit) paths it finds automatically, so in most cases you just need to check the list and fill any gaps by hand. Autodetection only runs when a path is empty — it never overwrites one you’ve set yourself — and it only covers Steam installs, so non-Steam copies still need to be pointed at manually.

You don’t have to fill in all of them — only the ones you mod. Empty entries are fine.

RPFM uses these paths to:

  • Load vanilla data for reference lookups, diagnostics and ITM detection.
  • Run the optimizer and the Tools (Faction Painter, Unit Editor, Translator) against a real game install.
  • Resolve where MyMod installs its packs.

You can still switch to a game that has no path set, but anything that reads vanilla data (diagnostics, dependency cache, optimizer, Tools, MyMod install) will either fail or produce empty/false results until you point RPFM at the install.

2. Pick a default game

Still in Preferences, in the General section, set Default Game. RPFM will switch to that game on launch and treat its schema as the active one. You can change the active game later from the Game Selected menu — see The main window.

3. Set the MyMod and Secondary paths

Two workspace paths live in the Paths section:

  • MyMod Base Path — folder where all your MyMod projects live. RPFM will create one subfolder per game inside it (e.g. MyMod/warhammer_3/, MyMod/three_kingdoms/). If you don’t plan to use MyMod, you can skip this — but most non-trivial mod projects benefit from the workspace structure MyMod provides. See What is MyMod?.
  • Secondary Folder — folder where mod launchers (Runcher and any other launcher that supports this feature) store the mods they manage, outside the game’s /data and /content directories. Pointing RPFM at it populates the Pack → Open From Secondary submenu with the Packs in that folder, so you can open them without digging through the filesystem. Skip it if your launcher doesn’t support a secondary folder.

4. Download or check schemas

Schemas are the per-game definitions RPFM uses to decode DB tables. They don’t ship with the program — they’re pulled in through the Update Manager, and they need updating every time Creative Assembly patches a game.

From the menu bar, run About → Check Updates. The Update Manager handles the program itself, schemas, and a couple of other auxiliary data sets (Lua autogen, Empire/Napoleon AK) from one dialog, with a button per component; press the schemas one to fetch the latest set from the rpfm-schemas repo. Do this on first launch to get the initial schemas, and again whenever a Total War game gets a content patch.

5. (Optional) Generate the dependency cache

Diagnostics, references and the dependency manager are dramatically more useful with a dependencies cache of the vanilla game files for the active game. Generate it once per game:

Game Selected → Generate Dependencies Cache

Generating it can take a few minutes — RPFM is reading and indexing every vanilla Pack. The cache is automatically invalidated whenever the game updates, so you’ll need to regenerate it after every patch.

6. (Optional) Set up the Assembly Kit (where supported)

Some tables — units, buildings, technologies — only exist in the Assembly Kit and not in the game’s Pack files. If you want diagnostics and references to cover those too:

  1. Install the Assembly Kit for the game from Steam (look for “Assembly Kit” or “Mod Tools” in the game’s tools list).
  2. In Preferences → Paths, set the Assembly Kit path for the game.
  3. Regenerate the dependencies cache (Game Selected → Generate Dependencies Cache). When an AK path is set, its data is folded into the same cache — there’s no separate step.

The AK isn’t shipped for every game. For Empire and Napoleon there’s no AK to install, but archived AK definitions are available as an optional download through the Update Manager (About → Check Updates) — grab them from there if you mod either game.

7. (Optional) Telemetry & crash reports

RPFM ships with two opt-out toggles in Preferences → Telemetry:

  • Enable Usage Telemetry — anonymous counters of which actions get used. Helps prioritise development.
  • Enable Crash Reports — automatic upload of panic reports to Sentry when something crashes. Helps fix bugs.

Both are on by default. Turning either off takes effect immediately.

You’re set

That’s the configuration done. The rest of the manual covers what you can actually do with the program — start with The main window for an orientation, or jump to a specific editor if you know what you’re after.

The main window

A quick orientation tour. Once you know where everything lives, the rest of the manual will make sense as it points at this or that panel.

The pieces

  1. Menu bar. Top of the window. Houses every global action — see below.
  2. Tab bar. Sits under the menu bar. One tab per open file.
  3. Pack tree. Left dock, top half. Shows the files inside the open Packs. Right-click anything in the tree for context actions; double-click a file to open its editor in a new tab.
  4. Dependencies. Left dock, under the Pack tree. Shows the contents of the active game’s dependencies cache (vanilla + parent mod Packs), so you can browse them alongside your own files.
  5. Editor area. The big central region. Whatever tab is active here drives most of what you can do.
  6. Side and bottom panels.
    • Diagnostics — bottom dock, visible by default. Lint-style warnings for the open Packs.
    • Global Search — right dock, hidden by default. Toggle from View or with its shortcut.
    • References — bottom dock, hidden by default. Populated when you ask “what points at this row?” from an editor.
    • Quick Notes — not a dock. It’s a side panel inside each file editor, toggled per-tab.
  7. Status bar. Bottom strip. Shows short-lived status messages from the last operation.

Split view

The editor area can show two tab panes side by side. Right-click a file tab and pick Move to Other Pane to send it to the second pane (or back to the first); Merge Panes moves every tab back into a single pane. A pane is only shown while it has tabs open.

Files you open from the Pack tree go to the pane that last had keyboard focus. A file is only ever open in one pane: opening it again focuses its existing tab, wherever it is. Each pane keeps its own preview tab. The split position is remembered between runs.

The welcome page

When no Pack is open, the editor area is replaced by a welcome page with quick links to recent Packs, the manual, the GitHub project, the Patreon (wink wink), and some other useful stuff.

The command palette

Press Ctrl+P (or Ctrl+Shift+P for the action variant) anywhere in the app to open the command palette. It’s a fuzzy finder for:

  • Open files (jump to any file inside the Pack by typing part of its path).
  • Available commands (run any menu action by typing its name).

For most “where is the menu item that does X?” questions, the command palette is the answer.

The menu bar

MenuWhat it covers
PackPack lifecycle: new, open, save (single Pack or all open ones), save for release, close, recent files, open from content / autosave / data / secondary, session picker, Pack settings, quit.
MyModThe MyMod project workflow: open the MyMod folder, create a new MyMod, import / export all open MyMods in one go, and a per-game submenu listing the existing MyMods for that game so you can open them directly. Deleting a MyMod now lives in the Pack tree’s right-click menu (on a MyMod Pack’s root). See What is MyMod?.
ViewToggle the bottom and side docks (Diagnostics, Global Search, References, Quick Notes, Dependencies).
Game SelectedSwitch the active Total War game, launch it, open its data / Assembly Kit folders, open RPFM’s config folder, and regenerate the dependencies cache.
ToolsIntegrated tools that span multiple files: Translator, Faction Painter, Unit Editor.
AboutManual, repo, Patreon, Discord, version info, check for updates.
DebugHidden by default. Enable it from Preferences → Debug to expose lower-level commands useful for development.

Connecting the dots

If you want a focused walk-through of a particular task, the rest of this section covers Pack-level workflows:

For the editors themselves (DB, Loc, Text, etc.), see the Editors section. For everything Pack-relationship-related (search, diagnostics, references), see Search & analysis.

Opening, creating and saving Packs

RPFM can have multiple Packs open at once. Most operations are scoped to a single Pack — the one currently selected. The Pack menu is your starting point for everything in this chapter.

Opening a Pack

The Pack menu has several entry points depending on where the Pack lives:

ActionWhen to use it
Open Packs…Standard file picker. Pick one or several Packs from anywhere on disk.
Open and Merge Packs…Pick several Packs and merge their contents into a single new in-memory Pack. Useful when you want to see what a stack of mods looks like as a whole.
Load All CA PacksOpen every vanilla Pack of the active game in one go. Read-only browsing — you can’t save into a vanilla Pack.
Open Recent ▸Quick access to the last few Packs you opened.
Open from Content ▸Browse Packs in your …/Steam/steamapps/workshop/content/ for the active game.
Open from Data ▸Browse Packs in the active game’s data/ folder.
Open from Secondary ▸Browse Packs in the secondary mods folder you configured.
Open from Autosave ▸Recover a Pack from RPFM’s autosave folder.

You can also drag-and-drop .pack files onto the main window to open them.

Multi-pack at once. When several Packs are open, the tree shows them as siblings. Diagnostics, search and dependencies all work across the open set, which is how you debug interactions between mods or compare a mod against vanilla.

Creating a new Pack

Pack → New Pack creates an empty Pack in memory. It isn’t on disk until you save it. The new Pack defaults to type Mod for the active game; to change the type or compression, right-click the Pack’s root in the Pack tree and use the Change PackFile Type and Compression Format submenus before saving.

Pack types

RPFM supports every Pack type the engine understands. The most common are:

  • Mod — the standard type for player-made mods. This is what you want 99 % of the time.
  • Movie — official types used by Creative Assembly themselves. Only used in mods to load certain stuff.
  • Boot — loads first, before everything else. Used by official content; rarely appropriate for player mods.
  • Release, Patch — official types used by Creative Assembly. Don’t use these for your own work unless you know exactly why.

The active type can be changed from the Change PackFile Type submenu in the Pack tree’s right-click menu (on the Pack’s root).

Saving

ActionEffect
Save Pack ▸ <name>Save the named Pack back to its current path.
Save Pack As ▸ <name>Save the named Pack to a new path. The original path is forgotten.
Save Pack For Release ▸ <name>Runs the optimizer against the Pack and then saves it. Use this for the Pack you actually upload to the Workshop.
Save AllSave every open Pack that has unsaved changes.
Close Pack ▸ <name>Close the named Pack. Closes silently — there’s no “save first?” prompt, so save before closing if you have unsaved changes you want to keep.

If you have many Packs open, use Save All rather than going through them one at a time.

Compression

Packs can be compressed with LZMA1, LZ4 or ZSTD (game-dependent), or left uncompressed. Pick the format from the Compression Format submenu in the Pack tree’s right-click menu (on the Pack’s root). RPFM will only auto-compress tables for Warhammer 3 and newer — older games crash on compressed table data, so the option isn’t offered for them.

Sessions

rpfm_server (the headless backend the UI talks to) holds all of your currently open Packs — together with their dependencies — inside a single server-side session. Pack → Select Session… lets you reattach to that session if the UI was disconnected, getting every Pack back in one go — useful after a rpfm_ui crash where the server was still running. See Server overview for the details.

Autosave

RPFM autosaves the open Packs at the interval configured in Preferences → General → Autosave Interval. Recover from Pack → Open from Autosave. Setting the interval to 0 disables autosave.

What’s next

Now that you can move Packs in and out, the next chapter covers what to actually do with them: The Pack tree.

The Pack tree

The Pack tree is the left-hand panel listing the contents of every open Pack. Files are grouped by their in-Pack path (DB tables under db/, locs under text/, etc.). The tree is the entry point to almost every per-file action in RPFM.

  • Double-click a file to open its editor in a new tab.
  • Single-click to select, and to open its editor in a preview tab. Multi-select with Shift (range) or Ctrl (additive).
  • Right-click to open the context menu (covered below).
  • Filter with the search box at the bottom of the tree. Matches against file paths; the tree collapses to show only matching entries.

Modified or newly-added items are marked with a thin vertical line on the right side of their row. Two states are drawn:

  • Added — new file/folder since the Pack was opened (or since the last save).
  • Modified — file with unsaved changes in the Pack (or a folder containing modified children). Modified takes priority over Added when both apply.

Both colours are configurable in Preferences → Appearance (Added and Modified colours, with separate light/dark theme entries). Folders inherit the most “active” state of their children. MyMod Pack roots also get an italic “MyMod” label drawn on the right side of the row.

Context menu

Right-clicking a file or folder gives you the actions below. Most also have keyboard shortcuts you can rebind in Preferences → Shortcuts.

The actions are grouped under three submenus (Add…, Create…, Open…) plus a flat list of file operations and Pack-level actions.

Add…

  • Add File — pick one or several files from disk and add them to the Pack at the selected location.
  • Add Folder — recursively add every file under a folder. The directory structure is preserved.

Create…

  • New Folder — create an empty folder inside the Pack.
  • New AnimPack / DB / Loc / Portrait Settings / Text — create an empty file of the chosen type at the selected location.
  • New Quick File — create a file with its type autodetected, based on the folder you have selected.

Open…

  • Open with Decoder — open the file in the DB Decoder. Used only for decoding tables.
  • Open Dependency Manager — opens the in-Pack dependency manager. See Dependencies.
  • Open Containing Folder — opens the folder containing your pack in your file manager.
  • Open with External Program — open the file in the external program you have configured in your OS for its type. Edits are reloaded back into the Pack when you save in the external editor.
  • Open PackFile Settings — opens Pack settings for the Pack the selection belongs to.
  • Open Notes — opens the Notes view for the Pack.

File operations

  • Rename/Move — rename the file or folder, or move it to a different in-Pack path. Renaming a folder rewrites every contained path; references in DB tables are not updated automatically.
  • Delete — remove the selection from the Pack.
  • Extract — write the selected files out to disk, preserving their tree structure.
  • Copy Path — copy the in-Pack path to the system clipboard.
  • Copy / Cut / Paste — clipboard operations within the Pack tree (or back into the same Pack at a new location).
  • Duplicate — create a copy of the selected file inside the Pack.
  • Copy To Pack ▸ <other Pack> — copy the selection into another open Pack. Useful when refactoring or splitting mods.
  • Merge Tables — only enabled with multiple DB tables of the same definition (or multiple Loc files) selected. Combines them into one and removes the originals. Useful for collapsing many small tables into a single one.
  • Update Tables — update the tables in the pack to their latest version supported by the game.
  • Generate Missing Loc Data — generates Loc entries for fields in your pack that are missing them.
  • Run Script ▸ <script> — run one of your plugin scripts against the selection. The submenu lists every script in your config scripts folder.

Pack-root actions

When you right-click the Pack root itself, additional Pack-level actions appear: Save Pack, Save Pack As…, Save Pack For Release…, Close Pack, Install / Uninstall, the Change PackFile Type submenu (Boot, Release, Patch, Mod, Movie, plus header flags), the Compression Format submenu (None, Lzma1, Lz4, Zstd, Lz4Block), the Special Stuff actions for the active game (Optimize PackFile, Rescue PackFile, Build Starpos, Patch Siege AI, Live Export, Pack Map, Update Anim IDs), and MyMod actions when the Pack is a MyMod.

Multi-pack operations

Some context-menu actions know about every open Pack:

  • Copy To Pack ▸ lists every other open Pack.
  • Cut+Paste moves files within a Pack at a new location.

The tree itself only supports drag-drop within a single Pack. To move files between Packs, use Copy To Pack or Cut+Paste.

Tips

  • The filter bar accepts substrings and regex. Use it to surface every loc, every animation, every file under text/db/, etc.
  • Use Expand All / Collapse All at the bottom of the context menu to quickly fold or unfold the tree.

Pack settings & notes

Each Pack carries a small bundle of metadata that lives inside the Pack itself. These are spread across a few different views and menu actions in the UI; this page covers them.

Pack file type, compression, header flags

These live on the Pack root’s context menu (right-click the Pack in the tree). They are not inside the Pack Settings tab.

Change PackFile Type

A submenu listing the possible PFH file types: Boot, Release, Patch, Mod, Movie. Mods almost always want Mod (shows up in the launcher) or Movie (always-on, hidden from the launcher). The other three are CA-only types and should not be used for player mods.

The same submenu also exposes the header flags as checkable items: Index Includes Timestamp, Index Is Encrypted and Data Is Encrypted (editable), and Header Is Extended (read-only). The encryption flags are applied the next time the Pack is saved, and are only available for PFH4 and newer Packs (Rome 2 and later games); older formats can’t be encrypted. The optimiser’s Encrypt the Pack option sets both flags for you (see Optimising a mod).

Compression Format

A submenu listing the supported compression formats: None, Lzma1, Lz4, Zstd, Lz4Block. Which formats actually work depends on the active game and the Pack version (Lz4Block is only for Shogun 2 Complete Edition PFH3 Packs, the rest only for PFH5 and newer Packs). The selected format is applied the next time the Pack is saved.

Pack Settings tab

Open Pack Settings (Pack root context menu → Open ▸ Open PackFile Settings) opens the Pack Settings as a tab in the main view. It’s a flat scrolling form that holds RPFM-specific behavioural settings for the Pack:

  • PackedFiles to Ignore on Diagnostics Check — paths (one per line, # for comments) that the diagnostics tool should skip. Supports per-folder, per-file, per-field and per-diagnostic ignore patterns. Files in this list still contribute reference data — they just aren’t analysed.
  • Files to Ignore when Importing — paths to skip when importing from a MyMod folder. MyMod-only.
  • Disable Autosaves for this PackFile — turns off autosaving for just this Pack.
  • Do Not Generate Existing Locs — when checked, Generate Missing Locs skips Loc entries that already exist in vanilla or in parent files.

Click Apply Settings at the bottom to save changes back into the Pack.

The fields shown here are loaded dynamically from the Pack’s settings, so the exact list may grow over time as new options are added.

Notes

Notes in RPFM are structured — not a single free-form text area, but a list of short notes, each with a message and an optional URL. Notes can be attached to The Pack as a whole — created from the tree’s context menu → Open ▸ Open Notes with the Pack root selected (or with no file selected).

Notes are persisted inside the Pack, are visible in RPFM, and are ignored by the game.

Quick Notes panel

The active file editor includes a Quick Notes side panel that lists the notes attached to the open file. Toggle it from the file tab’s right-click menu → Toggle Quick Notes.

Where the data lives

Pack settings, notes and the dependency manager are stored in a small set of RPFM-reserved files inside the Pack (settings.rpfm_reserved, notes.rpfm_reserved, dependencies_manager_v2.rpfm_reserved). The game ignores these; other tools that don’t know about RPFM will see them as normal files. They round-trip cleanly between RPFM versions.

These reserved files are only written for Packs of type Mod or Movie. Switching the Pack to Boot, Release or Patch before saving therefore strips them.

Dependencies

“Dependencies” in RPFM means two related but distinct things:

  1. The vanilla game data plus any parent mods you tell RPFM to consider when looking up references, running diagnostics, detecting ITM rows, and so on. This is the dependency cache.
  2. The list of parent Packs declared inside your Pack. This is the dependency manager.

This chapter covers both.

The dependency cache

Most “smart” features in RPFM (reference lookups, table validation, ITM detection, datacore handling, the diagnostics panel, the optimiser, global search across vanilla, MyMod install) need to know what the vanilla game contains. RPFM keeps a cached snapshot of that data per-game.

Generating the cache

Game Selected → Generate Dependencies Cache builds (or refreshes) the cache for the currently active game. The generation can take a few minutes — RPFM is reading every vanilla Pack of the game. Re-run it whenever:

  • The game gets a content patch.
  • The schemas got updated.
  • The cache appears empty/missing files in the Dependencies panel.

The cache is stored under RPFM’s config directory and is per-game.

Assembly Kit data

For games with an Assembly Kit, the cache build also picks up AK-only tables (units, buildings, technologies, etc. that aren’t shipped inside Packs) and stores them alongside the vanilla data. This happens automatically as part of Generate Dependencies Cache — there is no separate “Generate AK Database” command.

For this to work, RPFM needs to know where the Assembly Kit is installed. Set the path in Preferences → Paths → Assembly Kit for each game you have one for. If the path isn’t configured, the cache is still built but RPFM warns that diagnostics may produce false positives because some reference tables won’t be available.

The Dependencies panel

The Dependencies dock (toggle from View → Toggle Dependencies Window) lets you browse:

  • Game Files — vanilla Packs, indexed.
  • Parent Files — files contributed by parent mods you’ve declared.
  • Assembly Kit Files — Assembly-Kit-only tables and locs.

You can open any vanilla / parent / AK file in a read-only editor — useful for checking how Creative Assembly does things, or comparing your overrides against their source.

The in-Pack dependency manager

A Pack can declare other Packs as parents. The game itself ignores this list, but RPFM uses it to identify the packs to load as part of the dependencies when opening the pack.

To edit a Pack’s declared parents, right-click the Pack in the tree → Open ▸ Open Dependency Manager. It opens as a tab in the main view.

The manager is a small two-column table:

  • Load before ingame? — checkbox. Forces the game to load the pack as dependency if present. Alters the load order and is not recommended to check it.
  • Pack — the parent Pack file name (e.g. parent_mod.pack).

Order can matter for some games — when in doubt, list parents in load order.

Setting parents matters for diagnostics. Without them declared, RPFM treats parent-mod content as missing and may report false positives in the diagnostics panel. If you’re modding on top of someone else’s mod, declare it.

Putting it together

A typical workflow when you start a new mod project:

  1. Make sure the dependencies cache is built for your game (and that the Assembly Kit path is set in Preferences if the game has one).
  2. In your new Pack, open the Dependency Manager and declare any parent mods you depend on.
  3. From here on, Diagnostics, References, DB editor lookups and the Optimiser will all work correctly against your specific load order.

Plugin scripts

Experimental. Plugin scripts are a new, experimental feature. The way scripts are invoked and how data is handed to them may still change in future versions.

Plugin scripts let you run your own Python or Lua scripts against the files you have selected in the Pack tree. They’re meant for bulk, repetitive edits that would be tedious to do by hand — prefixing every Loc entry, rewriting a column across many tables, batch-renaming references, and so on.

Installing a script

Drop a .py (Python) or .lua (Lua) file into the scripts folder inside RPFM’s config folder. RPFM creates the folder for you the first time it’s needed; you can also reach the config folder quickly via Game Selected → Open RPFM’s Config Folder.

The interpreter is chosen from the file extension, so python (for .py) or lua (for .lua) must be available on your system PATH. The bundled Lua examples run on Lua 5.1 through 5.4.

You’ll find ready-to-use examples in the examples/plugin_scripts/ folder of the RPFM repository.

Running a script

  1. Select one or more files/folders in the Pack tree.
  2. Right-click and open the Run Script submenu. It lists every script currently in your scripts folder (or No scripts found if the folder is empty).
  3. Pick a script. RPFM runs it and reads the results back into the Pack.

How a script is run

  1. The files you selected are extracted to a temporary folder that mirrors their in-Pack layout:
    • DB and Loc tables are written as TSV (tab-separated, quoting disabled), exactly like the normal Extract action.
    • Everything else is written as raw binary.
  2. Your script is launched with the extracted file paths as arguments (python <script> <paths...> or lua <script> <paths...>).
  3. When the script finishes, RPFM reads the (possibly modified) files back into the Pack. Open views of changed files are reloaded and the Pack is marked as modified.

Files the script deletes are left untouched in the Pack, and files it creates anew are ignored — only the originally-selected files are read back.

The TSV format

When a script receives a DB table or Loc file, it gets a TSV with two header rows before the data:

key<TAB>text<TAB>tooltip          <- column names
#Loc;1;text/db/mymod.loc<TAB><TAB> <- metadata: #table_type;version;path
mymod_key1<TAB>Hello world<TAB>false

Rows are \r\n-terminated and values are never quoted (so they can’t contain tabs or newlines). Keep the two header rows intact when editing, or RPFM won’t be able to read the table back.

Debugging

Every run writes the script’s output to last_run.log inside the scripts folder (overwritten each run). It contains the script path, exit status, and the full stdout/stderr. If a script exits with a non-zero status, RPFM also shows its stderr in a dialog. The same output is logged to the RPFM Server terminal.

Lua tests

Experimental. Lua tests are new, and the way they emulate the game may still change in future versions.

Lua tests let you check what your mod’s scripts do without starting the game. RPFM runs your scripts together with the game’s own script libraries, fakes the game around them, and tells you whether they did what you expected.

Lua tests need the game’s Assembly Kit, as RPFM uses its scripting docs to know what the game’s functions return. Only campaign scripts are supported for now.

Writing a test file

A test file is a .lua file that describes the situation to test, and what your scripts should do in it. Keep test files outside script/, for example in a tests/ folder of your Pack, so the game never loads them:

-- Code outside tests runs before the game starts: set up the factions and regions your scripts need.
local kislev = rpfm.faction { key = "wh3_main_ksl_kislev", is_human = true }
local empire = rpfm.faction { key = "wh_main_emp_empire", is_human = false }

rpfm.test("gives gold to Kislev at the start of its turn", function()
    rpfm.fire("FactionTurnStart", { faction = kislev })
    rpfm.assert_called("treasury_mod", "wh3_main_ksl_kislev", 500)
end)

rpfm.test("doesn't give gold to AI factions", function()
    rpfm.fire("FactionTurnStart", { faction = empire })
    rpfm.assert_not_called("treasury_mod")
end)

This tests a mod script like this one, in script/campaign/mod/kislev_gold.lua:

core:add_listener(
    "kislev_gold",
    "FactionTurnStart",
    function(context) return context:faction():name() == "wh3_main_ksl_kislev" and context:faction():is_human() end,
    function(context) cm:treasury_mod(context:faction():name(), 500) end,
    true
)

How a test runs

Every test gets its own, fresh game. For each one, RPFM:

  1. Runs the top-level code of the test file, creating the factions, regions and characters it sets up.
  2. Loads the game’s script libraries, then the mods of your open Packs (script/campaign/mod/), like the game does.
  3. Triggers NewSession, WorldCreated, UICreated and FirstTickAfterWorldCreated, so your mods’ listeners are registered and their first-tick code has run.
  4. Runs the test’s function.

Nothing done by a test is left behind for the next one: globals, listeners, timers and recorded calls all start from scratch.

The test API

Setting up the world

FunctionWhat it does
rpfm.test(name, function)Registers a test.
rpfm.faction { key = ..., ... }Creates a faction. Any other field is what the method with the same name returns, like is_human = true or region_list = { my_region }.
rpfm.region { key = ..., ... }Creates a region, with fields like rpfm.faction.
rpfm.character { ... }Creates a character, like rpfm.character { faction = kislev }.
rpfm.mock(object, method, value)Changes what a method of a faction, region or other game object returns, like rpfm.mock(my_region, "owning_faction", kislev). value can also be a function, called with the arguments of each call.

Fields can be plain values, other objects, or functions called with the arguments of each call. Methods returning lists, like region_list or character_list, can be given plain arrays: they’re turned into the list the game would return, with num_items, is_empty and item_at.

local kiev = rpfm.region { key = "wh3_main_combi_region_kislev", is_province_capital = true }
local katarin = rpfm.character { character_subtype_key = "wh3_main_ksl_katarin", rank = 10 }

local kislev = rpfm.faction {
    key = "wh3_main_ksl_kislev",
    is_human = true,
    treasury = 2000,
    region_list = { kiev },
    character_list = { katarin },

    -- Called with the arguments the script passed.
    has_effect_bundle = function(bundle) return bundle == "my_bundle" end,
}

rpfm.mock(kiev, "owning_faction", kislev)
rpfm.mock(katarin, "faction", kislev)

Factions and regions from the game’s database exist even if your test doesn’t create them, so scripts looking them up by key, like with cm:get_faction, find them. Anything a test doesn’t set up returns a neutral value of the type the scripting docs say, like false, 0, an empty string, or a null object.

Making things happen

FunctionWhat it does
rpfm.fire(event, { ... })Triggers an event, like the game does. The table has what the event’s context returns, like { faction = kislev }.
rpfm.advance_time(seconds)Moves game time forward, running the timers that finish in that time, like the ones from cm:callback.
rpfm.end_turn()Plays a full round: every faction, in the order they were created, starts and ends its turn, with the turn events of its regions and characters.

rpfm.end_turn() also moves the turn number forward, and triggers these events, in this order:

  1. WorldStartRound.
  2. FactionRoundStart, for every faction.
  3. For each faction: FactionTurnStart, RegionTurnStart for each of its regions, CharacterTurnStart for each of its characters, FactionBeginTurnPhaseNormal, FactionAboutToEndTurn, CharacterTurnEnd for each character, RegionTurnEnd for each region, and FactionTurnEnd.

Only the regions and characters given to the faction’s region_list and character_list get their events.

Checking the results

FunctionWhat it does
rpfm.assert_called(method, ...)Fails unless your scripts called the game’s method with those arguments, like rpfm.assert_called("treasury_mod", "wh3_main_ksl_kislev", 500).
rpfm.assert_not_called(method)Fails if your scripts called the game’s method.
rpfm.calls_to(method[, type])Returns the arguments of every call to the game’s method, for your own checks. type limits it to calls on objects of that type, like "FACTION_SCRIPT_INTERFACE".
rpfm.assert_equal(actual, expected[, message])Fails unless both values are equal. message is shown in front of the error, to tell which check failed.

rpfm.assert_called only checks the arguments you give it, so rpfm.assert_called("treasury_mod", "wh3_main_ksl_kislev") passes whatever the amount was. When it fails, it lists the calls that were made instead:

expected a call to treasury_mod("wh3_main_ksl_kislev", 500), got treasury_mod("wh3_main_ksl_kislev", 250)

Your scripts’ own globals are there too, so tests can inspect and change them directly, like enabling a single feature of your mod before checking it. Any other Lua error in a test, like one from error() or assert(), fails it too.

Examples

Timers

Scripts using cm:callback or cm:repeat_callback only run their callbacks when game time moves forward:

-- The mod: warns the player 10 seconds after the campaign starts, and every 30 seconds after that.
my_mod_warnings = 0

cm:add_first_tick_callback(function()
    cm:callback(function() my_mod_warnings = my_mod_warnings + 1 end, 10)
    cm:repeat_callback(function() my_mod_warnings = my_mod_warnings + 1 end, 30, "my_mod_reminder")
end)
-- The test.
rpfm.test("warns after 10 seconds, then every 30", function()
    rpfm.advance_time(5)
    rpfm.assert_equal(my_mod_warnings, 0, "after 5 seconds")

    rpfm.advance_time(5)
    rpfm.assert_equal(my_mod_warnings, 1, "after 10 seconds")

    rpfm.advance_time(80)
    rpfm.assert_equal(my_mod_warnings, 4, "after 90 seconds")
end)

For timers reported through events, rpfm.advance_time triggers TimeTrigger with the timer’s id in context.string, like the game does.

Several turns

local kislev = rpfm.faction { key = "wh3_main_ksl_kislev", is_human = true }

rpfm.test("pays every turn, for three turns", function()
    rpfm.end_turn()
    rpfm.end_turn()
    rpfm.end_turn()

    rpfm.assert_equal(#rpfm.calls_to("treasury_mod"), 3)
    rpfm.assert_equal(cm:model():turn_number(), 4)
end)

Changing the world during a test

rpfm.mock can be used inside a test, to change the world between events. Here, a mod that should only reward a faction once it owns a region:

local kislev = rpfm.faction { key = "wh3_main_ksl_kislev", is_human = true }
local kiev = rpfm.region { key = "wh3_main_combi_region_kislev" }

rpfm.test("rewards Kislev once it owns Kislev", function()
    rpfm.fire("FactionTurnStart", { faction = kislev })
    rpfm.assert_not_called("apply_effect_bundle")

    rpfm.mock(kiev, "owning_faction", kislev)
    rpfm.mock(kislev, "region_list", { kiev })

    rpfm.fire("FactionTurnStart", { faction = kislev })
    rpfm.assert_called("apply_effect_bundle", "my_reward_bundle", "wh3_main_ksl_kislev")
end)

Checking the calls yourself

rpfm.calls_to gives you every call, for checks the assertions can’t do, like the order or the number of calls:

local kislev = rpfm.faction { key = "wh3_main_ksl_kislev", is_human = true }
local ungol = rpfm.faction { key = "wh3_main_ksl_ungol", is_human = true }

rpfm.test("pays every human faction once per round, in turn order", function()
    rpfm.end_turn()

    local payments = rpfm.calls_to("treasury_mod")
    rpfm.assert_equal(#payments, 2, "number of payments")

    -- Each entry has the arguments of a call, in order.
    rpfm.assert_equal(payments[1][1], "wh3_main_ksl_kislev")
    rpfm.assert_equal(payments[2][1], "wh3_main_ksl_ungol")
end)

Calls are recorded when they reach the game, so library functions like cm:treasury_mod show up under the name of the game function they end up calling, which is usually the same.

UI events

UI events, like clicks, carry the name of the component in context.string, which can be set like any other accessor:

rpfm.test("clicking my button opens my panel", function()
    rpfm.fire("ComponentLClickUp", { string = "my_mod_button" })
    rpfm.assert_equal(my_mod.panel_open, true)
end)

Testing a single feature

As tests see your scripts’ globals, they can reach inside your mod, like to change its settings or call its functions directly:

rpfm.test("the difficulty setting doubles the gold", function()
    my_mod.settings.difficulty = "hard"
    my_mod.pay_faction(kislev)
    rpfm.assert_called("treasury_mod", "wh3_main_ksl_kislev", 1000)
end)

Running tests

Select one or more test files in the Pack tree, right-click them and choose Run Lua Tests. The results window lists every test, with:

  • Errors: why it failed. Tests also fail when one of your scripts errors while the test runs, or while the game starts.
  • Undocumented calls: game functions your scripts called that the scripting docs don’t describe. They return placeholder values, so a test depending on them may not match the game.
  • Output: everything the scripts printed, with print or out. For long runs, only the latest lines are kept.

Errors of the game’s own scripts while starting are listed separately, and never fail your tests: they usually mean the fake game lacks something those scripts need.

Tests can also be run from tools and AI agents connected to the server, through the LuaRunTests command or the lua_run_tests MCP tool. These can also load the vanilla scripts of a campaign, like main_warhammer, by passing its name. That’s closer to the game, but slower, and more likely to hit parts of the game the harness doesn’t fake. Running tests from the Pack tree only loads the script libraries and your mods.

Limitations

The game is faked, not run, so keep in mind that:

  • Only what your tests set up exists, besides the factions and regions of the game’s database. Armies don’t move, battles don’t happen, and methods like treasury_mod are recorded but don’t change what treasury returns, unless your test mocks it.
  • Methods the scripting docs don’t have return placeholder values, and are listed under Undocumented calls. Asking a documented object for a method it doesn’t have returns nil, like in the game.
  • Scripts can’t run programs, load DLLs, or touch your files. Files they write are kept in memory, and can be read back during the same test.
  • string.len, string.sub and string.find count characters instead of bytes, and string.find doesn’t use patterns, like in the game. The original versions are available as string.len_lua, string.sub_lua and string.find_lua.
  • Scripts running for too long, like when stuck in a loop, are stopped, failing the test.

Editors overview

Every supported file type opens in a dedicated view that knows how to decode, present and re-encode that format. Views live in tabs in the central panel; the Pack tree is where you launch them from.

How editors behave

  • Open by double-click. Double-clicking a file in the Pack tree opens it in a new tab. Re-opening an already-open file just brings its tab forward.
  • Split view. Tabs can be moved to a second pane to see two files side by side. See Split view.
  • Tabs do not survive Pack switches. Editors stay open until you close them, or you close the Pack underneath. RPFM warns before destroying unsaved changes.
  • Save are usually on-edit, except in some editors. Editors save on edit the changes to the in-memory Pack, except some editors which have a save button at the bottom of the screen. You still need to save the Pack for changes to hit the .pack file on disk.
  • External editing. From the tree’s context menu, Open with External Program extracts the file into a temp folder and hands it to your OS’s default app for that file type. Edits round-trip back into the Pack when you save them externally. There is no per-extension configuration in RPFM Preferences — the OS decides which app opens what.

Supported file types

File typeEditor chapterUI today
DB tableDB tablesFull grid editor
LocLoc filesFull grid editor
Text & scriptsText & scriptsKTextEditor (full)
Image (DDS, PNG…)Images & DDSViewer only (replace via tree)
Video (CA_VP8)VideoMetadata + IVF round-trip
AnimPackAnimPackInner tree + per-file editors
AnimsTableAnimationsJSON debug view
AnimFragmentBattleAnimationsStructured form view
MatchedCombatAnimationsJSON debug view
Portrait SettingsPortrait SettingsFull structured editor
Audio (.wav, .ogg…)AudioPlay/stop player
RigidModelRigidModelMetadata + glTF export (read-only)
UnitVariantSpecialised editorsFull structured editor
AtlasSpecialised editorsFull grid editor
ESF (saves, startpos)Specialised editorsTree editor (debug-gated)
BMDSpecialised editorsJSON text editor
UICSpecialised editorsRead-only text dump
Group formationsSpecialised editorsFull structured editor with canvas

JSON debug view means the file decodes via the lib, gets serialised to pretty JSON, opens in a text editor, and you save it by hitting Save (the JSON is parsed back into the typed structure). It’s editable but it’s not a UI — handle with care.

For the live list of formats rpfm_lib understands and their lib-side capability, see the rpfm_lib::files API docs. A format with full lib support may still only have a debug view in the UI.

The DB Decoder

A separate, lower-level tool for reverse-engineering binary table layouts. Used when a game patch changes a table format and the schema needs an update. See The DB Decoder.

DB tables

The DB editor is the heart of RPFM. Every Total War mod that changes unit stats, tech effects, building chains, faction colours, ability cooldowns or hundreds of other things does it by editing rows in a DB table.

What’s a DB table?

A binary file under db/<table_name>_tables/<file_name> inside a Pack. Each table has a schema-defined set of typed columns and a list of rows. RPFM resolves the schema for the active game on load, decodes the rows, and presents them as a grid you can edit like a spreadsheet.

Layout

The editor itself is just the grid plus a small status bar at the bottom (line counter, flagged-rows filter button). There is no top toolbar — every action lives in the grid’s right-click context menu, plus a couple of pop-up panels:

  • Filter bar at the bottom of the grid, with Use Regex and Case Sensitive toggles and a column-group selector. Filter matches across the selected column group.
  • Search panel (Ctrl+F) — a pop-up find/replace bar over the grid; column-aware. For project-wide replace use Global Search.
  • Sidebar panel — a per-column hide / freeze checkbox grid. Open it from the right-click menu’s Sidebar entry. Frozen columns stay pinned to the left of the grid as you scroll.

The grid’s vertical header (row numbers) is always visible; the horizontal header is visible by default but column visibility/freezing is driven from the Sidebar panel rather than from a header right-click menu.

Right-click context menu

Right-click anywhere in the grid for the full action set. Highlights:

  • Add Row / Insert Row / Delete Row / Delete Filtered-Out Rows
  • Clone ▸ Clone & Insert Row / Clone & Append Row
  • Copy ▸ Copy / Copy as LUA Table / Copy to Filter Value
  • Paste / Paste as New Row
  • Generate IDs, Rewrite Selection, Revert Values, Reset Selected Values, Invert Selection
  • Resize Columns
  • Import TSV / Export TSV
  • Search (Ctrl+F opens the search bar)
  • Sidebar (toggles the per-column hide/freeze panel)
  • Find References — opens the References panel for the selected cell.
  • Cascade Edition — rename a key everywhere it’s referenced; see below.
  • Patch Columns — write a schema patch for the selected column without modifying the schema itself.
  • Go To ▸ Go To Definition / Go To File / Go To Loc (when a matching loc column exists for the row).
  • New Profile — save the current column visibility / sort / filter as a named profile.
  • Undo / Redo.

Editing rows

The grid behaves like a spreadsheet:

  • Click to select, double-click to edit.
  • Tab / Shift+Tab move between columns; Enter commits.
  • Range selection with Shift-click; column / row selection by clicking the header.
  • Copy / Paste with Ctrl+C / Ctrl+V. RPFM supports pasting from Excel / LibreOffice / Google Sheets — copy cells from a spreadsheet and paste them straight in.
  • Sort by clicking a column header.

Type-aware editing

Each column edits according to its declared type:

TypeEditor
BooleanCheckbox in the cell.
Integers/floatsNumeric editor with format validation; rejects bad input.
StringInline text editor.
ColourRGBInline RGB editor + colour picker popup.
Reference (FK)Auto-completing combo box pulling values from the referenced table.
SequenceSub-table opened in a popup editor. Sequence cells can be copied and pasted between rows like any other cell.

Lookups & references

When a column references another table (a foreign key), RPFM:

  • Shows the referenced value’s lookup field (e.g. the unit’s display name) as part of the cell.
  • Surfaces invalid references as red highlights and as Diagnostics entries.

The global default for “show lookups vs raw keys” is PackFile → Settings → Table → Enable Lookups.

Cascade Edition

Renaming a row’s key is normally a footgun: every other table that references that key will break. Cascade Edition does it safely.

  1. Select the cell whose key you want to rename.
  2. Right-click → Cascade Edition.
  3. RPFM finds every reference to the old key across the active Pack and its parents, and asks for confirmation.
  4. On confirm, the key is renamed everywhere atomically.

Use this whenever you rename a unit, building, faction, technology, or any other keyed entity that’s referenced by other tables.

TSV round-tripping

Export a DB table to TSV when you want to:

  • Edit it in a spreadsheet program with formulas.
  • Diff it in git as plain text.
  • Run a script that produces the table mechanically.

The TSV header carries enough metadata for RPFM to re-import the file as the right table type. Re-importing replaces the table’s content wholesale.

Patches

If a column needs different display behaviour (a different lookup, a tooltip, a default value, …) without changing the table’s column layout, a schema patch overrides the field metadata at runtime. Patches ship inline inside each per-game schema file — there’s no separate overlay file to load. The right-click Patch Column action saves your override into a local per-game patch file under your config directory, which gets layered on top of the schema-shipped patches the next time the schema is loaded.

When the schema is wrong

If a table refuses to decode or shows obviously wrong values, the schema for that table is likely out of date.

  1. About → Check Updates to pull the latest schemas (along with the program, lua autogen, and old-AK updates).
  2. If the table was changed recently and no schema is available yet, open the file in the DB Decoder to update the definition manually.

See also: Diagnostics, References, Global Search.

Loc files

Loc files (anything ending in .loc — vanilla puts them under text/, mods conventionally use text/db/ or text/) hold the localisation strings the game shows to the player. The Loc editor is a simplified DB editor with three columns: key, text, and tooltip.

The columns

  • Key. The string the game uses to look up the entry. Must be unique within the Pack. Conventionally namespaced (e.g. units_descr_short_text_my_unit).
  • Text. The actual displayed string, in whatever language this loc file is for. Supports the engine’s inline tags ([[col:red]], [[/col]], etc.) — RPFM doesn’t validate them, but Diagnostics will catch obviously broken pairs.
  • Tooltip. Unknown. Historically called tooltip for some reason.

Editing

Same controls as the DB editor: row add/remove/clone, find & replace, filter, sort by column, paste from spreadsheets, TSV round-tripping. The text columns are wide by default since most rows have multi-word values.

Diagnostics for locs

Several entries in the Diagnostics panel target locs specifically:

  • Empty key / empty text — usually a typo or an accidental row.
  • Duplicated key — two rows with the same key in the same Loc file.
  • Invalid escape sequences — usually [[col:…]] tags without a matching closer.

Run diagnostics after a translation pass to catch the easy mistakes.

TSV workflow for translators

The recommended flow for translating a mod is:

  1. Open the mod in RPFM.
  2. Use Tools → Translator to extract translatable strings into a structured editor (see Translator) — this is the best path for full mod translations because it integrates with the Total War Translation Hub.

If you really want raw TSV instead, every Loc editor has Export TSV — translate in a spreadsheet, then Import TSV to bring the translation back in.

Text & scripts

Anything text-shaped opens in the text editor. RPFM uses KTextEditor under the hood, which means proper syntax highlighting, line numbers, find/replace, multi-cursor and the rest of the modern code-editor toolkit — for free, on every supported language.

What counts as “text”

RPFM auto-detects text-shaped files by extension. The editor handles, among others:

  • Scripts — Lua, Python, batch files, shell.
  • Markup — XML, HTML, JSON, YAML, TOML, INI.
  • Shaders — HLSL, GLSL, CG, FX.
  • CA-specific — .battle_script, .twui, .twui.xml, .kfa, .kfc, .kfp, .tweak, .environment.
  • Plain — .txt, .md, .csv (when not opened as a table).

For the full list, see the rpfm_lib::files::text module.

Features

The KTextEditor backend gives you:

  • Syntax highlighting for every detected language.
  • Find / replace (Ctrl+F / Ctrl+R) with regex support.
  • Multi-cursor / column selection (Ctrl+Alt+click).
  • Bookmarks and quick-jump.
  • Code folding for languages that support it.
  • Indent / unindent (Tab / Shift+Tab).
  • Open / save integration with the Pack — your edits go into the in-memory Pack on save.

Lua scripts

When a diagnostics check runs, its results for the open script are shown in the editor itself, like in an IDE: the offending code is underlined, and its line gets a mark in the margin. See Diagnostics for the checks performed.

Hovering a documented function, accessor or event in a Lua script shows its docs. Both features rely on the scripting docs of the game’s Assembly Kit, completed with the vanilla scripts from the dependencies cache, so they need the Assembly Kit path set and the cache generated. To run a mod’s scripts outside the game, see Lua tests.

External editor

If you’d rather use VS Code, Neovim or anything else, Open with External Program from the Pack tree’s right-click menu extracts the file to a temp folder and opens it with your OS’s default app for that extension; saving it externally pushes the change back into the Pack. There’s no per-extension override inside RPFM — the OS picks the app.

Images & DDS

The image editor is a viewer that handles most image formats CA uses, including DDS, PNG, JPG and the various atlas formats.

What it does

  • Preview the image — most DDS formats decode to PNG for display, including the BC1–BC7 compressed variants.
  • Zoom with the mouse wheel; pan by dragging.

What it doesn’t do

There’s no in-app pixel editor. To edit an image:

  1. Extract it from the Pack (Pack tree → Extract).
  2. Open it in your image editor of choice (GIMP, Photoshop, Krita…).
  3. Save it back to disk.
  4. Add it back to the Pack at the same path (Pack tree → Add File…), or use Open with External Program to wire your editor up so the round-trip happens automatically.

Atlases

.atlas files are layout maps for sprite sheets — each entry references a region of an associated DDS. They open in their own simple table editor (under Specialised editors) rather than the image viewer.

Video (CA_VP8)

.ca_vp8 is a Creative Assembly–specific container around a VP8 video stream — used for movies, intros and similar. RPFM has full read/write support and can convert to and from the standard .ivf container.

What you can do

  • Inspect stream metadata: width, height, frame count, framerate, codec version (v0 or v1).
  • Convert to and from IVF — can convert CA_VP8 files to and from IVF files, editable with ffmpeg.
  • Check video integrity — verify the stream parses cleanly. Useful when debugging a video the game refuses to play.

Editing a video

There’s no in-app video editor. To re-encode a CA_VP8:

  1. Convert the file to IVF (button in the editor toolbar).
  2. Open the IVF in your video tool of choice and produce a new VP8 stream. FFmpeg works:
    ffmpeg -i input.mp4 -c:v vp8 -b:v 2M output.ivf
    
  3. Convert back from IVF in the editor to bake the new stream into the .ca_vp8.

AnimPack

An AnimPack (.animpack) is a container file CA uses to bundle animation-related game data — animation tables (AnimsTable), animation fragments (AnimFragmentBattle), matched-combat tables, and the raw .bin animation files referenced by them — into a single archive the engine can load efficiently. From a modder’s perspective it’s a “Pack inside a Pack”.

Layout

The editor opens as a single tab with two side-by-side panels and an instructions banner across the top.

  • Left panel — a live view of the main contents tree of every open Pack. It mirrors what you see in the dockable Pack tree, including all open Packs simultaneously.
  • Right panel — the contents of the AnimPack itself. Read-only display: you can browse and filter, but you can’t open files into editors from here.

Each panel has its own filter line edit, case-sensitive toggle, auto-expand-matches toggle, and Expand-all / Collapse-all shortcuts.

What you can do

The editor exposes exactly three operation:

  • Copy a file into the AnimPack — double-click a file in the left panel. The file is copied from the open Pack into the AnimPack.
  • Copy a file out of the AnimPack — double-click a file in the right panel. The file is copied out of the AnimPack into the open Pack at the same path.
  • Delete a file from the AnimPack — select it on the right panel and press Del (the action is shortcut-bound; there is no visible context menu).

If the open AnimPack is from vanilla (GameFiles) or a parent mod (ParentFiles) rather than your own Pack, the copy-in action is silently disabled — you can only modify AnimPacks you own. Copy-out from a vanilla AnimPack into your own Pack is allowed.

Which Pack the operations target (multiple Packs open)

Every operation resolves the target Pack from the left panel’s own selection inside the AnimPack tab (or, if nothing is selected there, the first editable Pack in the panel). The dock’s selection is irrelevant here.

  • Copy in: source files come from the Pack of the file you double-clicked on the left panel, and the AnimPack inside that same Pack is what gets modified.
  • Copy out: the file is written into the Pack currently selected (or first) on the left panel of the AnimPack tab. If that Pack doesn’t have an AnimPack at the same path, the operation errors.
  • Delete: operates on the AnimPack inside the Pack currently selected (or first) on the left panel.

So to switch which Pack you’re working against, click a node belonging to that Pack inside the AnimPack tab’s left panel — not in the dockable contents tree.

Caveats

  • Renaming files inside an AnimPack is not supported in the editor — you’d have to copy out, rename in the parent Pack, copy back in, and delete the original.

Animations

Three closely related animation file types share a chapter but each have different UI maturity in RPFM today: AnimsTable, AnimFragmentBattle, and MatchedCombat.

If you’re new to TW animation modding, the practical order is: AnimsTable defines the animation library; AnimFragmentBattle says how those animations apply to a creature in battle; MatchedCombat orchestrates the synchronised “two units fighting each other” animations.

AnimsTable

UI is a JSON debug view: the file decodes via the lib, gets serialised to pretty JSON in a text editor, and saving parses the JSON back into the structured form. Round-trip works, but there is no row-grid UI yet — you edit JSON.

AnimFragmentBattle

A proper structured editor — the only one of the three that has a custom UI. The view exposes the fragment’s metadata (skeleton name, locomotion graphs, identity flags) as form widgets, and the per-entry table as an embedded grid.

Diagnostics for AnimFragmentBattle catch:

  • Missing locomotion graph references.
  • Missing referenced animation files.
  • Missing sound files.

MatchedCombat

UI is a JSON debug view, same model as AnimsTable. The lib has different decode paths per game (Three Kingdoms, Warhammer 3, and a default for the rest) so the JSON shape varies, but the editor is the same generic JSON editor.

Anim & legacy formats

.anim files (the raw animation streams the others reference) currently have no UI viewer at all in RPFM. The lib has read support so they’re decodable programmatically, but double-clicking one in the tree will not open it.

Portrait Settings

Files matching *portrait_settings*.bin (typically under ui/) describe how a character’s portrait is composed in-game: which art set the character belongs to, which variants of that art set are available, the head-and-body camera setup for each entry, and which texture files supply the diffuse and mask layers for each variant. The Portrait Settings editor gives you a structured view instead of a binary blob.

Layout

The tab is one widget split between a left list of entries and a right detailed view.

  • Entries list (left) — one row per “art set entry”, keyed by its art_set_id. The id is what tables like campaign_character_arts_tables.art_set_id reference. A filter line edit sits above the list.
  • Detailed view (right) — driven by the entry currently selected on the left. Contains, in order:
    • Head camera group box — z, y, yaw, pitch, distance, theta, phi, fov, skeleton node.
    • Body camera group box (checkable — toggle the title bar to enable/disable the body camera for this entry) — z, y, yaw, pitch, fov, skeleton node.
    • Variants group box — its own filtered list of variants belonging to this entry. Selecting a variant fills the rest of the panel.
    • Variant fields — diffuse file, mask 1 / 2 / 3 file paths (line edits), season (string), level (spinbox), age (spinbox), politician (checkbox), faction leader (checkbox).
    • Texture previews — four group boxes (diffuse, mask 1 / 2 / 3) that render the actual texture from the referenced path (with a fallback search that swaps /portholes/ for /units/). The previews refresh ~500ms after you finish editing the path.

Which fields are visible depends on the file’s binary version: older versions hide the fields that didn’t exist yet (e.g. version 1 has only the spherical head-camera fields and no body camera; version 4 omits season/level/age/politician/faction-leader and the spherical head fields). The editor adjusts visibility automatically on load.

Operations

All operations are right-click context menus on the two list views, not toolbar buttons. There is no toolbar.

On the entries list:

  • Add — create a new empty entry.
  • Clone — duplicate the selected entry (disabled until you have a selection).
  • Delete — remove the selected entry (disabled until you have a selection).
  • Delete filtered-out rows — once a filter is active in the entries list, delete every entry that the filter is currently hiding. Useful for trimming an entry set down to only the rows that match a pattern.

On the variants list:

  • Add, Clone, Delete — same semantics, scoped to the selected entry’s variants. There is no “delete filtered-out rows” on the variants list.

Beyond that:

  • Edit fields — change spinboxes, line edits, checkboxes directly. All changes are buffered into the selected list item and committed when you save the file. There is no separate “apply” step.
  • Filters — the entries filter and the variants filter are independent line edits with case-insensitive substring matching, debounced behind a short timer.

Diagnostics integration

The Portrait Settings checks in Diagnostics cover:

  • Datacored Portrait Settings file — the file is identical to a vanilla one and adds nothing.
  • Invalid Art Set Id — the entry’s art_set_id isn’t declared in campaign_character_arts_tables.art_set_id (across vanilla, parents, and the open Pack).
  • Invalid Variant Filename — a variant’s filename isn’t declared in variants_tables.variant_filename (or variant_name, as a fallback for older games).
  • Missing texture file — for each variant, separate checks for the diffuse and the three masks: the referenced path doesn’t exist anywhere in the open Pack, parents, or vanilla.

For a tidy mod, run diagnostics after a portrait pass — it’ll catch most of the small mistakes that produce missing-portrait icons in-game.

Audio

CA’s audio pipeline is built on Wwise and ships as several related artefacts inside Packs:

  • Sound banks (.bnk) — Wwise-format containers with the audio assets.
  • Sound bank databases — table-shaped indices that match bank IDs to game-side names.
  • Sound events — DB-shaped tables the rest of the game references when something needs to play.

In RPFM today the only dedicated UI is a play/stop media player for individual audio files (.wav, .ogg, …).

What you can do

  • Play / stop any audio file the editor recognises (the audio editor view is just two toolbar buttons).

What you can’t (yet)

  • Author or repackage a .bnk from raw audio inside RPFM.
  • Inspect a .bnk’s internal event list, listed assets, or other internal metadata.

For new audio content, use Asset Editor.

RigidModel

.rigid_model_v2 (RMV2) is CA’s in-engine 3D model format — used for units, monsters, buildings, props and effectively everything you see on the battle map. RPFM reads and writes versions 6, 7 and 8 of the format. Pre-RMV2 model formats (the ones in Empire and Napoleon) are not supported.

Layout

The tab is a horizontal splitter: the editor form on the left, and (when enabled) the 3D preview on the right.

The editor form contains:

  • RMV group box — the format version combobox (8 / 7 / 6) and the Export to glTF button.
  • LOD tree — every LOD in the file as a top-level node (Lod 0, Lod 1, …); each LOD’s mesh blocks appear as children, labelled with the material name (e.g. Mesh Block 0 (Material: weighted_skin)). Selecting a LOD or a mesh block fills the detail panels on the right; switching selection auto-saves the previous selection’s edits back into the in-memory model.
  • Detailed view group box (per-LOD) — visibility distance, authored LOD number, quality level. Active when any LOD or mesh block is selected.
  • Mesh block group box (per-mesh-block) — mesh name, texture folder, shader name. Only active when a mesh block is selected, not the LOD root.
  • Textures table — two columns (texture_type, texture_path) listing the textures attached to the selected mesh block’s material. Editable like a regular table view.

Editing

The editor is editable, not read-only — for files inside your own Pack. When a file is loaded from GameFiles or ParentFiles, the save path is a no-op (you can change the fields, but they won’t be persisted).

What you can change from the UI:

  • Per-LOD: visibility distance, authored LOD number, quality level.
  • Per-mesh-block: mesh name, texture directory, shader filters.
  • Per-texture: texture type and path, via the textures table.
  • Per-file: format version (re-encode as 6, 7 or 8 via the combobox).

What is not editable from the UI: vertex data, bones, geometry, attachment points, custom material parameters, cloth/grass/collision-specific data. To make changes there, author the model in Blender / 3ds Max / Maya with the appropriate CA exporter and import the resulting .rigid_model_v2 into your Pack.

3D preview

The 3D preview is gated by both:

  • The support_model_renderer Cargo feature at build time. Default release builds may or may not include it depending on the platform — the renderer pulls in a separate native library that isn’t on every target.
  • A runtime setting (enable_renderer in preferences). Even when the build supports it, the renderer is only spun up when this setting is on.

When both conditions are met and the model loads, the renderer attaches as a second pane in the splitter and stays in sync with the file’s in-memory state on save and reload. Failures to load the renderer are non-fatal — the editor still works, just without the 3D pane.

glTF export

Export to glTF in the RMV group box converts the current in-memory model to glTF 2.0 (one scene per LOD) and writes it to a path you pick via a Save dialog. Useful for opening CA models in Blender or other standard 3D tooling.

The same conversion is available programmatically via rpfm_extensions::gltf for tools that want to batch-convert outside the UI.

Specialised editors

Several less-common file types have dedicated views that didn’t warrant their own chapter. Grouped here for completeness.

UnitVariant

Variant mesh definitions for units — which RigidModel goes with which faction colour scheme, kit, etc. The view is a proper structured editor: a categories list on the left, a variants list in the middle for the selected category, and a per-variant form on the right (mesh file, texture folder, an unknown numeric value). Add / clone / delete on both lists from their right-click menus.

A separate read-only JSON debug view for the same file type also exists behind a settings, in case you want to edit the file more freely.

Atlas

Sprite-sheet layout files. Each row defines a region (UV rectangle + name) within an associated texture. Atlas files open in the regular DB editor — full grid editing, TSV round-trip, the whole DB editor toolset.

ESF

.esf is CA’s binary format for save games and startpos files. Massive, deeply nested, and slow to parse. RPFM exposes it as a tree of nested nodes with typed leaves (ints, floats, strings, arrays).

Disabled by default. The ESF editor lives behind the Enable ESF Editor toggle in PackFile → Settings → Debug. Turn it on first; otherwise opening an .esf falls back to the JSON debug view.

  • Browse the full structure as a tree.
  • Edit leaf values in place via a side detail panel.
  • Filter within the loaded ESF (substring + regex toggle, plus an “auto-expand matches” option).

Writing is supported but slow on big files (a multi-hundred-MB save can take a few seconds). Most ESF editing is for startpos files in a campaign mod context.

BMD

Battle Map Definition files — the binary scene description for battle maps (terrain, props, lighting). The current view is a JSON text editor: the file decodes via the lib, gets serialised to pretty JSON, and saves parse the JSON back. Editable but unstructured.

Group formations

Formation templates the AI (and multiple-selection drag-outs) use to deploy armies. Supported for Shogun 2, Rome 2, Attila, Thrones of Britannia, Troy, Pharaoh, Three Kingdoms and Warhammer 3.

Each formation is a set of blocks:

  • Absolute blocks sit at a fixed position. Every formation needs at least one, as it anchors the rest.
  • Relative blocks sit at an offset from another block, their parent.
  • Spans group other blocks, so relative blocks can be positioned from the whole group.

The editor has three columns:

  • Formations: the list of formations, with add, clone and delete. Formations with issues get an error or warning icon.
  • Canvas and blocks tree: the canvas draws a simulated deployment of the selected formation over a grid in meters, with the front at the top. The tree below shows each block under its parent. Selection is shared between both.
    • Drag blocks to move them. Their offsets snap to the grid step set above the canvas. Dragging a span moves all its members.
    • Shift+drag from a block to another to make the second one its parent.
    • Drag on empty space to select several blocks, and right-click for the block actions (add, add span over the selection, delete, delete with children).
    • Units per Block changes how many units the simulation assumes, so you can check how the formation stretches.
  • Inspector: the properties of the selected formation, container or span, including AI purposes, unit category requirements, supported subcultures and factions (picked from the ones in the game and the open Packs), and each block’s entity preferences.

Files opened from the game or from dependencies are read-only. Edits can be undone with Ctrl+Z. Deleting a block re-attaches its children to the closest surviving ancestor, keeping them in place. Issues like missing references or blocks depending on each other in a loop are reported in the Diagnostics panel.

The simulated layout assumes a relative block’s offset is a gap measured from its parent’s edge, and that a zero offset centers it on the parent. This hasn’t been confirmed in-game yet, so the canvas may not match the game exactly.

If you need the raw data, enable Use Debug View for Group Formations in the settings to open these files as JSON instead.

Animation file formats summary

For convenience, here’s where each animation-shaped format lives in the manual:

FormatEditor chapter
AnimPackAnimPack
AnimsTableAnimations
AnimFragmentBattleAnimations
MatchedCombatAnimations
.anim raw streamNo UI viewer today

The DB Decoder

The DB Decoder is a low-level tool for reverse-engineering or fixing a DB table’s binary layout. You only need it when:

  • A game patch changed a table’s columns and the schema is now wrong (rows decode as garbage, or fail outright).
  • You’re adding support for a previously unknown table.

For routine editing, you want the DB editor. The Decoder is a development tool.

Opening the Decoder

Right-click a DB file in the Pack tree → Open ▸ Open with Decoder. The file opens in its own tab and the decoder takes over the whole tab.

Layout

The decoder isn’t a “preview-the-table” view. It’s a byte-walker: a cursor moves through the file, and you commit one field at a time to the schema. The tab is laid out as:

  • Hex pane (top-left) — three monospace columns: byte offsets, raw hex bytes, and the ASCII rendering. The header bytes are shaded one colour, the bytes already covered by the schema you’re building are shaded another (yellow on light themes), and the next byte the cursor is sitting on is shaded a third (magenta on light themes).
  • Fields table (top-right) — one row per field already committed to the schema. Columns include Field Name, Field Type, First Row Decoded (the value at that position in row 1 of the file), Is key?, Ref. to Table, Ref. to Column, Lookup Columns, Default Value, Is Filename, Filename Relative Path, CA Order, Description, Bitwise Fields, Enum Data, Is Part of Colour. Right-click a row for Move Up / Down / Left / Right / Delete.
  • Current Field Decoded (middle) — for the bytes at the current cursor position, this panel shows what they would decode to as every supported type, side by side. Each row is one type, with a line edit showing the candidate value and a Use this button that commits that type as the next field and advances the cursor.
  • PackedFile Info (right) — table type, an editable version (spinbox), and the entry count read from the header.
  • Versions list (right, bottom) — every other version of this table that the schema already knows about. Right-click for Load definition (replace the in-progress definition with that version’s) or Delete definition (remove that version from the schema).
  • Buttons (bottom row) — Import from Assembly Kit, Test Definition, Remove all fields, Finish it!.

There is no live preview of the decoded table.

Workflow

1. Read the header

DB files start with: an optional GUID block, an optional version block, a one-byte flag, and a u32 entry count. The decoder reads this for you on open and pre-shades that region in the hex pane — your cursor starts immediately after it.

2. Pick a starting point (optional)

Three shortcuts before you start decoding by hand:

  • Import from Assembly Kit — if the AK ships a definition for this table, load it and let the decoder render it. Most often the right starting point.
  • Versions list → Load definition — if a previous version of this table is in the schema, load it and only fix what changed (CA usually adds columns rather than reshuffling the layout).

3. Commit fields one at a time

For each field, look at the Current Field Decoded panel and pick the row whose value looks plausible (a string that reads as text; an integer in a believable range; a float that isn’t 1e-39). Hit Use this on that row. The cursor advances by the size of that type, the field appears in the fields table, and the panel updates with the next set of candidates.

The supported types are: Boolean, F32, F64, I16, I32, I64, OptionalI16, OptionalI32, OptionalI64, ColourRGB, StringU8, StringU16, OptionalStringU8, OptionalStringU16, SequenceU32. There is no I8, the colour type is ColourRGB (no alpha), and the only sequence variant is SequenceU32.

After committing, edit the row in the fields table to fill in the Field Name (this is what becomes the column header in the DB editor) and any other metadata you have — key flag, references to other tables, default value, etc.

If you commit the wrong type, right-click the field and use Move Up / Down to reorder, Delete to remove it (which rewinds the cursor by that field’s size), or just hit Remove all fields and start over.

4. Test

Test Definition re-decodes the current file with the in-progress definition. On success you get a “Seems ok.” dialog. On failure you get the decoder error, with extra context for incomplete decodes (so you can see how far it got before things went wrong).

The decoder doesn’t iterate over multiple files for you — to validate the definition against vanilla or other parent files, save it first, then open one of those files in the regular DB editor.

5. Save

Finish it! writes the definition into the active schema for the version shown in the PackedFile Info spinbox, and reloads any DB editor tabs already open on this table so they pick up the new definition.

6. Submit upstream

Schema changes saved locally only live in your install until you submit them. PR them to rpfm-schemas so everyone benefits — see Schemas & patches.

Tips

  • Most schema breakages are “CA inserted a new column” rather than a wholesale layout change. Load the previous version from the versions list and walk the cursor up to the suspected insertion point — the candidates panel will tell you where the layout diverges.
  • SequenceU32 columns are recursive: a u32 count followed by that many copies of an inner sub-row. Decode them last and expand them in the fields table to define the inner fields.

Global Search

The Global Search panel finds — and optionally replaces — text across every supported file type in the open Packs (and, optionally, in vanilla and parent mods). It’s the closest thing RPFM has to a project-wide grep.

Toggle the panel from View → Toggle Global Search Window (default shortcut Ctrl+Shift+F).

Pattern & options

  • Pattern. What to match. By default it’s a literal substring search.
  • Use Regex. Treat the pattern as a regular expression instead.
  • Case sensitive. Off by default.

There is no “match whole word” toggle — wrap the pattern in word boundaries yourself in regex mode (\bword\b) if you need that behaviour.

Sources

A search can target several sources independently:

  • Per-Pack checkboxes. One checkbox per open Pack appears under the Sources group; tick the ones you want included. Newly opened Packs are ticked automatically, unless Auto-select Open Packs in Global Search is disabled in the settings.
  • Game Files. Vanilla files for the active game (requires the dependencies cache).
  • Parent Files. Parent mods declared by the active Pack (see Dependencies).
  • Assembly Kit Files. AK-only tables and locs (requires the AK install path to be configured for the active game).

You can mix any combination. The result tree groups matches per source.

File types covered

The search has a per-file-type checkbox for every editable type RPFM knows about. There’s also a Search on All master toggle and a Search on All Common toggle (DB / Loc / Text — the day-to-day editable types).

For DB tables and Loc files the matches are cell-level with row context; for text-style files matches are line-level; for the structured types (Atlas, Portrait Settings, etc.) matches are field-level inside the file’s structure.

Working with results

Each match in the results tree is clickable: clicking a DB cell opens the table and selects the cell, clicking a Loc match opens the right loc file at the right key, and so on.

Multi-select results to scope a follow-up replace.

Replace

Two modes:

  • Replace match — replace the currently-selected match(es) with the replacement text.
  • Replace all matches — replace every match in the result set in one shot.

Replace operations only run on Packs (Game / Parent / AK sources are read-only and skipped automatically).

Replace is destructive. Make sure you’ve saved (or have a recent autosave) before doing a project-wide replace, especially with regex.

Diagnostics panel

Diagnostics is RPFM’s static analyser for Packs. It runs a battery of checks across the open Packs and surfaces structured warnings and errors — invalid references, missing locs, broken portrait variants, datacore mistakes, and dozens of other classes of mod bug.

Toggle the panel from View → Toggle Diagnostics Window.

What it checks

Diagnostics is grouped internally by check category (one variant of DiagnosticType per category):

CategorySample checks
TablesOutdated table version, invalid foreign-key references, empty rows, empty keys, keys with invalid characters (jumplines, tabs, spaces, or trailing whitespace), duplicated rows, datacore vs. master mismatches, tables on the modding deny-list, table-name issues (trailing number, contains space), required fields left empty, altered / out-of-spec tables, dangling file-path field references, invalid escape sequences in text.
Pack-levelInvalid Pack name, invalid file names, missing loc data for referenced keys, ITM (identical-to-master) files, files overwriting vanilla unintentionally, duplicate files.
Portrait SettingsArt sets / variants not declared in the corresponding tables, missing texture references, datacored portrait_settings files.
Group FormationsFormations without an absolute block, blocks referencing missing or later blocks, blocks depending on each other in a loop, duplicated block ids or formation names, empty spans, blocks whose minimum units exceed their maximum, and blocks without entity preferences.
AnimFragmentBattleMissing locomotion graphs, missing animation files, missing sound files, missing metadata file.
TextLua scripts: syntax errors, invalid DB keys (in --@db-annotated tables and in arguments of documented functions), unknown methods on cm, core and game interfaces, wrong argument counts, and listeners for events that don’t exist. Everything past syntax errors needs the game’s Assembly Kit, whose scripting docs describe the API.
DependenciesPack declares a parent that doesn’t exist on disk.
ConfigMissing or outdated dependency cache, dependency cache failed to load, wrong game path.

Every diagnostic carries a severity (Info, Warning, Error), a category, the file path it applies to, the row/cell/field it relates to, and a short description.

Running diagnostics

  • Run all — checks every file in every open Pack.
  • Run on open files — limited scope; useful in big Packs.
  • Auto-run — two togglable triggers in PackFile → Settings → Diagnostics:
    • Trigger diagnostics on Pack open
    • Trigger diagnostics on table edit (only fires while the diagnostics dock is visible)

There are no “on file add” or “on Pack save” auto-run triggers.

Triaging results

Click a column header to sort the results by that column (severity, file, type, etc.), so you can group everything of one kind together or push all the errors to the top.

Double-click a diagnostic to jump to the offending file / row / cell. Right-click for the ignore actions:

  • Ignore Parent Folder / Ignore Field for Parent Folder — skip every diagnostic on every file in the parent folder, optionally narrowed to a specific field.
  • Ignore File / Ignore Field for File — skip every diagnostic on this file, optionally narrowed to a specific field.
  • Ignore Diagnostic for Parent Folder / Ignore Diagnostic in Field for Parent Folder — skip just this specific diagnostic type across the parent folder, optionally narrowed to a field.
  • Ignore Diagnostic for File / Ignore Diagnostic in Field for File — same, scoped to one file.
  • Ignore Diagnostic for Pack — skip this diagnostic type across the whole Pack.

There is no “Fix automatically” or “Copy diagnostic” action today — every fix is manual.

Where ignores live

Each ignore action appends one line to the Pack’s Files Ignored on Diagnostics Check setting (the multi-line text field exposed in the Pack Settings tab — see Pack settings & notes). The Pack Settings tab also documents the per-file / per-field / per-diagnostic syntax if you want to write entries by hand.

These ignores live inside the Pack itself, survive across RPFM restarts, and travel with the .pack to anyone else who opens it.

Reading severities

  • Error — something that will cause a problem in-game.
  • Warning — likely a problem, but might not be a problem in-game.
  • Info — heads-up; noteworthy but not necessarily wrong.

If you’re shipping a mod, aim for zero errors and as few warnings as possible. Info entries are usually fine to leave alone.

References panel

The References panel answers the question “where else is this referenced?” for any DB cell. It’s the modder’s “find all usages” — invaluable before deleting, renaming, or changing a row whose key something else depends on.

Toggle the panel from View → Toggle References Window.

In any DB editor, right-click a cell whose value is a key (typically a primary key or a foreign-key value) and choose Find References. The References panel populates with every other place that key appears — across the open Pack, parent mods, vanilla, and (where relevant) the Assembly Kit.

Reading the results

Each result is a row in a small table with these columns:

  • Data Source — PackFile, ParentFiles, GameFiles, or AssKitFiles.
  • Path — the in-Pack path of the file containing the reference.
  • Column Name — the column that’s referencing the key (DB-only; blank for loc/filename refs).
  • Row Number — the row containing the reference.

(There’s also a Column Number column, hidden by default — you can re-show it from the table header’s right-click menu if you need it.)

Click a result to jump to it.

Common workflows

  • Before deleting a row. Run a reference search on the row’s key. If anything in your Pack references it, you’ll need to clean those up first. References from vanilla / parent mods mean the row probably shouldn’t be deleted at all.
  • Before renaming a row. Same as above. If the references are all in your own Pack, Cascade Edition (right-click → Cascade Edition in the DB editor) can do the rename across them in one shot.
  • Auditing a new feature. After adding a new unit, run a reference search on its key to confirm it’s wired up everywhere it should be (cost table, recruitment table, faction availability, etc.).
  • Understanding a vanilla mechanic. Open a vanilla DB table from the Dependencies panel, reference-search a key that interests you, and follow the trail.

Limitations

References are computed from declared schema relationships — RPFM knows that column X in table A references column Y in table B because the schema says so. Hard-coded references in script files (Lua) or text-formatted UI files won’t show up unless the schema knows about them.

For text-only references (e.g. a Lua script that looks up a unit by string), use Global Search instead.

Quick Notes

The Quick Notes panel surfaces the active file’s notes inline at the side of the editor, so you don’t need to open the dedicated notes view every time you want to read or jot something down.

Toggle it from the file tab’s right-click menu → Toggle Quick Notes. There is no main-menu View entry for it, and no global shortcut by default — every editor tab gets its own panel that you turn on per-tab.

What notes actually are

Notes in RPFM are a structured list, not a single free-form text area. Each note has:

  • A short message (the body).
  • An optional URL (for linking out to a wiki page, issue tracker, etc.).
  • An implicit attachment: either to the Pack as a whole, or to a specific in-Pack path.

The Quick Notes panel for a file shows every note attached to that file’s path, plus a + to add a new one and right-click Edit / Delete on each entry.

What it shows

When the focused editor changes (e.g. you switch tabs), each tab’s Quick Notes panel is independent and shows the notes for that tab’s file. There’s no automatic fall-back to “Pack notes” — Pack notes are accessed through the dedicated Notes view (see below).

For files opened from the Dependencies panel (vanilla / parent / AK files) Quick Notes is read-only and effectively empty: notes only exist for files that live in an open Pack.

Difference from “Open Notes”

  • Open Notes (Pack tree context menu → Open ▸ Open Notes) opens a dedicated tab containing the same structured list view. Use this for browsing every note attached to a Pack, or for working with notes when you don’t have the relevant file open.
  • Quick Notes panel is the same view embedded inside the active editor tab so you can read / jot without leaving the file.

A reminder about Save for Release

Save for Release does not strip notes. It runs the optimiser dialog before saving but leaves the Pack’s notes file (notes.rpfm_reserved) intact for Mod and Movie Packs. Notes are dropped only if you change the Pack type to Boot / Release / Patch before saving — at which point the notes section, the dependency manager and the per-Pack settings are all skipped.

Notes are intended for your own bookkeeping; if you don’t want them shipping with the Pack, delete them or change the Pack type before release.

What is MyMod?

A MyMod is RPFM’s idea of a mod project — a Pack that lives in a structured folder on disk, with optional Lua / Sublime / VSCode / Git scaffolding around it, and a few extra menu actions you don’t get with a plain Pack.

You can absolutely mod without MyMod. But for any non-trivial project, MyMod gives you:

  • A predictable folder layout per game (<MyMod base>/<game key>/<mod name>/).
  • A spot to keep raw asset files (PSDs, source XMLs, scripts in their working form) alongside the Pack they end up in.
  • Optional Sublime / VSCode project scaffolding for Lua mods (via the tw_autogen Lua API definitions).
  • Optional Git repository initialised inside the MyMod folder, with a configurable .gitignore.
  • Per-MyMod Import (folder → Pack) and Export (Pack → folder) actions, so you can work on the mod’s contents as files on disk and then bundle them back up.
  • Per-MyMod Delete that removes the whole folder via the system trash.
  • Open and Install/Uninstall through dedicated menu entries.

The folder layout

When you create a MyMod, RPFM creates the folder and any opt-in scaffolding you ticked in the new-MyMod dialog:

<MyMod base>/<game key>/
├── <mod name>.pack                 the Pack itself (saved when you save the MyMod)
└── <mod name>/
    ├── .git/                       only if Git support was enabled
    ├── .gitignore                  only if Git support was enabled
    ├── .vscode/extensions.json     only if VSCode support was enabled
    └── <mod name>.sublime-project  only if Sublime support was enabled

The Pack itself also gets a per-Pack “Files Ignored on Import” setting populated from the dialog, so the Sublime/VSCode/Git scaffolding files are skipped when you later Import the folder back into the Pack.

The <MyMod base> is configured once in PackFile → Settings → Paths → Extra Paths → MyMod base path (First-time configuration). The game-key folders are created on demand the first time you save a MyMod for that game.

When to use MyMod (and when not to)

Use MyMod when:

  • You’re starting a new mod and want a clean workspace.
  • You want one-click Lua / VSCode / Sublime / Git scaffolding.
  • The mod has source assets you want to keep alongside the Pack and round-trip via Import/Export.

Skip MyMod when:

  • You’re making one-off edits to an existing Pack you already have on disk.
  • You’re inspecting / debugging someone else’s Pack.

Active MyMods are per-Pack

A Pack is in MyMod mode when its on-disk path lives inside the MyMod base folder under one of the game-key subfolders. The mode is tracked per-Pack on the server: open a normal Pack and a MyMod side by side, and the MyMod-only context-menu actions appear only when you right-click the MyMod’s root.

The MyMod menu in the menu bar is mostly about creating new MyMods, opening existing ones, and bulk-running Import/Export across every open MyMod. The per-Pack actions (single Import, single Export, Delete, Open MyMod Folder) live in the Pack root’s right-click menu.

Next

Creating and managing a MyMod

The MyMod actions are split between the MyMod menu in the menu bar (creation, opening, bulk operations) and the Pack root’s right-click menu (per-Pack operations on a MyMod).

The MyMod menu

In the menu bar the MyMod menu has, in order:

  • Open MyMod Folder — opens the configured <MyMod base> in your file manager.
  • New MyMod — creates a new MyMod (covered below).
  • Import All Open MyMods — runs Import on every Pack currently open in MyMod mode.
  • Export All Open MyMods — runs Export on every Pack currently open in MyMod mode.
  • (separator)
  • <Game name> submenu — one per supported game, only visible if you have at least one MyMod for that game. Each submenu lists the .pack files inside <MyMod base>/<game key>/ as one click-to-open entry per mod.

New MyMod, Import All and Export All are disabled until a MyMod base path is configured in PackFile → Settings → Paths → Extra Paths.

Creating a MyMod

MyMod → New MyMod opens the new-MyMod dialog. The fields are:

  • Game — combobox of supported games (Arena is excluded). Defaults to the currently-selected game.
  • Mod Name — short identifier. Used as both the folder name and the Pack file name. No spaces allowed; the Accept button is disabled if the name contains spaces or is already in use.
  • Lua Support group:
    • Create Sublime Text Project — drops a <mod_name>.sublime-project in the folder.
    • Create VSCode Project — drops .vscode/extensions.json in the folder, recommending the Lua and Code Runner extensions.
    • When either is enabled, RPFM also expects a .luarc.json to live there (auto-added to the import-ignore list).
  • Create Git Repository With GitIgnore group (checkable) — when enabled, RPFM git inits the folder and writes a .gitignore. You can either type your own .gitignore contents in the GitIgnore Contents textedit, or tick Same as files ignored on import to mirror the import-ignore list.
  • Files Ignored on Import — paths (one per line, relative to the MyMod folder) that RPFM should skip when later running Import. The Sublime/VSCode/Git scaffolding paths are appended automatically.

On confirm, RPFM:

  1. Creates <MyMod base>/<game key>/<name>/ on disk.
  2. Initialises .git, .gitignore, .vscode/, <name>.sublime-project as opted in.
  3. Creates an empty Pack and saves it as <name>.pack inside the folder.
  4. Stores the import-ignore list in the Pack’s settings.
  5. Switches the active Game Selected to match the MyMod’s game.
  6. Opens the Pack in a new tab and marks it as a MyMod on the server.

Switching MyMods

Use the per-game submenus in MyMod → <Game name> → <mod name> to open an existing MyMod. The previously-open Packs (MyMods or otherwise) are left untouched — opening a MyMod just opens that Pack alongside the others.

Per-MyMod actions (right-click the Pack root)

When you right-click the root of a Pack that’s in MyMod mode, the context menu adds four MyMod-only entries (greyed out / hidden for non-MyMod Packs):

  • Import — folder → Pack for this MyMod. See Import / Export.
  • Export — Pack → folder for this MyMod. See Import / Export.
  • Delete Selected MyMod — moves the entire MyMod folder to the system trash after a confirmation. Cannot be undone from inside RPFM.
  • Open MyMod Folder — opens the MyMod’s folder in your file manager.

Installing to the game folder

Install / Uninstall are not MyMod-specific menu items — they’re plain Pack actions that show up in the Pack root’s right-click menu for any saved Pack:

  • Install copies <pack path> into <game install>/data/<pack name> so you can launch and test.
  • Uninstall removes that copy.

For a “release” install (with the optimiser dialog), use Pack → Save Pack For Release before installing — see Pack operations.

Import / Export

MyMod’s Import and Export operations move content between a .pack file and a folder of files on disk. They’re the bridge between “Pack as opaque archive” and “mod content as files I can keep in version control or run scripts against.”

Both operations are per-Pack and only available when the Pack is in MyMod mode. There are two ways to invoke each:

  • Right-click the Pack root in the tree and pick Import or Export — affects just that one MyMod.
  • MyMod → Import All Open MyMods / MyMod → Export All Open MyMods — runs the same operation across every Pack currently open in MyMod mode.

There is no single-Pack equivalent in the menu bar — only the per-Pack right-click and the bulk menu-bar entries.

Export — Pack → folder

Export writes every file inside the MyMod’s Pack out to disk under the MyMod folder, preserving the in-Pack tree as on-disk subfolders.

After export you’ll have something like:

<MyMod base>/<game>/<mod>/
├── <mod>.pack                  the Pack itself (unchanged)
├── db/
│   ├── units_tables/my_units
│   └── ...
├── text/
│   └── my_mod.loc
├── script/
│   └── ...
└── ...

DB tables export as TSV by default, and will be parsed from TSV on import automatically.

Why export?

  • Version control. Track files individually in git rather than as a single binary blob.
  • External tooling. Run a script that processes a folder of files (validators, formatters, generators).
  • Bulk renames or refactors that are easier to do with shell tools than with RPFM’s UI.
  • Diffing two Packs — export both and diff -r.

Import — folder → Pack

Import is the reverse: scan the MyMod folder, collect every file under it (excluding any path listed in the Pack’s Files Ignored on Import setting), and add them to the Pack. Files at the same in-Pack path are replaced with the on-disk version.

Import is additive. Files that exist in the Pack but no longer exist on disk are not deleted. If you’ve removed a file from the on-disk folder and want it gone from the Pack too, delete it from the Pack manually (right-click → Delete).

The ignore list

The Files Ignored on Import list is the per-Pack setting you (optionally) populated when you created the MyMod. It lives in the Pack itself and is editable later via Open PackFile Settings (Pack settings & notes).

The new-MyMod dialog also auto-appends entries for any Sublime / VSCode / Git scaffolding you opted in to, so you don’t accidentally import .git/, .vscode/, the Sublime project file, or .luarc.json back into the Pack.

Round-tripping

Export → edit on disk → Import is the standard “I want to script changes to this mod” workflow. As long as the on-disk layout matches the in-Pack layout, RPFM treats it as a faithful round-trip.

Importing a folder that was exported from a different RPFM install (e.g. via git from a teammate) works the same. The MyMod folder layout is the contract — and the per-Pack ignore list lives inside the Pack itself, so it travels with the .pack.

Caveats

  • Import is additive, not destructive. Removing a file from the on-disk folder won’t remove it from the Pack on the next Import — delete it from the Pack manually.
  • Binary files (DDS, RigidModel, AnimPack, BMD, …) are exported as-is. Edit them in the appropriate external tool, save them back to the same path, and re-import.

Translator

The Translator is RPFM’s dedicated tool for translating mods. It’s a structured editor on top of a Pack’s loc data, plus an integration with the Total War Translation Hub so the resulting translations can be shared and applied automatically by Runcher.

Open from Tools → Translator with a Pack open.

What it does

The Translator presents every translatable string in the Pack as a structured row, with:

  • Key — the loc key.
  • Source text (src) — the original (typically English) text.
  • Translation (dst) — your translated text.
  • Needs retranslation (retr) — boolean. Set when the source text changed since the translation was last saved (the “outdated” state).
  • Removed (rem) — boolean. Set when the key was once translated but the mod no longer contains it (the “unused” state). The translation is kept in the JSON so it can come back if the key reappears.
  • Auto-translated (aut) — boolean. Set when the Translator filled the row automatically (from vanilla loc data or a translation service) and the user hasn’t reviewed it yet. Editing the translation clears the flag.

The JSON file also records its format version. 0 is the legacy format (compatible with older Translation Hub tooling); 1 is the current format, which adds the aut flag, the source language (src_lang), the list of authors (typed comma-separated in the Authors field) and the glossary. It’s selectable from the translator UI for English sources, and new translations default to 1. Switching it rewrites the file in the chosen format on the next save.

The workflow

  1. Auto-translate from vanilla. For keys that exist in vanilla loc data and are unchanged, the Translator will auto-translate them using the vanilla translations, leaving the modded or altered lines to be translated.
  2. Translate row by row, or using one of the translation integrations. AI translations are told which game the mod is for, and to prefer the game’s official translations for its terms and names. The batch button in the toolbar auto-translates every outdated line at once with DeepL, AI or Google Translate. By default it only fills lines with no translation; lines with an outdated translation are left for you to fix, unless Overwrite existing outdated translations is checked in its menu. Batch results are marked as auto-translated so you can review them. A progress dialog shows how far along it is and lets you cancel it. Requests are spaced out, and when a service rate-limits them RPFM slows down and retries. DeepL gets up to 50 lines per request, so it needs far fewer requests; if one of those requests fails, all its lines are reported as failed. At the end, a results dialog shows how many lines were translated, failed, or were left unprocessed, with the failed lines and their errors in its details.
  3. Generate the translated loc. When you save, the Translator writes a translated .loc file into the Pack at the right path: text/!!!!!!translated_locs.loc for Warhammer 1 and newer (except Thrones of Britannia), or text/localisation.loc for Thrones of Britannia and older games. The translation works in-game immediately.
  4. Persist the translation as JSON. The translation is also persisted to <config>/translations_local/<game>/<pack>/<SOURCE>-<LANGUAGE>.json (or <LANGUAGE>.json for format 0). This is the file you contribute to the Translation Hub.

Editing a line

Clear translation resets the selected line to untranslated. It has a configurable shortcut, unbound by default.

To keep the editor compact, the quick start instructions, the auto-translation settings (the ones applied when a line is selected) and the formatted in-game style previews of the source and translated text are hidden by default. Show them with the Help, Auto-translate settings and Preview toggles.

Glossary

The glossary is a per-translation list of source terms and the translation you want used for each one, like faction or unit names. Open it with the Glossary button: it opens over the right side of the translator, so the lines stay visible for reference, and its close button hides it again. Edit it like any other table, with its toolbar, right-click menu or shortcuts. Entries without a source term are ignored.

When auto-translating, the glossary is used like this:

  • DeepL: with the Use a DeepL glossary toolbar button enabled (the default), RPFM stores the glossary in your DeepL account and DeepL enforces its terms. There’s one per Pack and language pair, named RPFM <game>/<pack> <source>-<target> <fingerprint>; it’s reused while the glossary doesn’t change, and replaced when it does. Entries without a translation are left out of it. If it can’t be stored, or the button is disabled, the glossary is sent as a hint instead.
  • AI: the glossary is included in the prompt, as a hint.
  • Google Translate: doesn’t support it.

Review glossary terms in hint-based results, as the service may not always follow them.

The glossary is saved with the translation, and only format 1 can store it. With format 0 the Glossary button is disabled.

Translating from a language other than English

When opening the Translator, you pick the language the mod is written in. The last one you picked is preselected.

The Translation Hub only ships vanilla texts in English. For any other source language, RPFM extracts them from the game’s own local_<language> packs the first time you pick that language. It saves them to <config>/translations_local/<game>/vanilla_<language>.tsv, so they stay available after the game no longer has that language installed.

Many games only install the language they’re set to. If the source language isn’t installed, the Translator warns you that vanilla lines won’t be auto-translated. To get those texts:

  1. Change the game’s language to the source language in Steam (Properties → General → Language) and let Steam download it.
  2. Open the Translator with that language as source. RPFM saves its vanilla texts and tells you when it’s done.
  3. Switch the game back to your language, and open the Translator again.

Sharing through the Translation Hub

Once you’ve finished a translation, click Submit to the Translation Hub in the translator:

  1. RPFM saves the translation, and shows a summary: the pack, the languages, how many lines are translated, and warnings about lines still pending or auto-translated and not reviewed. Fill in the authors there if you haven’t, so you get credited, and click Submit.
  2. The first time, RPFM asks you to sign in with GitHub. It opens GitHub in your browser and copies a code to your clipboard: paste it there and approve RPFM. You only need to do this once. RPFM keeps the sign-in in your system’s keyring, and you can sign out from Settings > GitHub.
  3. RPFM opens a pull request on the Translation Hub with your translation, through your own fork of it, which it creates the first time. If you submit the same translation again while its pull request is open, the pull request is updated instead.
  4. Once the pull request is accepted, any Runcher user with Enable Translations turned on will get the translation automatically applied at launch — without altering the mod, and with outdated lines silently ignored. No more “I installed a translation pack but the mod was updated and now half the lines are wrong.”

For step-by-step instructions including screenshots, see the Translating a mod tutorial.

Faction Painter

The Faction Painter lets you edit faction colour schemes — primary / secondary / tertiary banner colours, uniform colours, and the related per-faction display knobs — without manually grinding through factions_tables and the colour columns.

Open from Tools → Faction Painter with a Pack open.

Workflow

  1. Open the Pack you want to add faction colour overrides to.
  2. Open Faction Painter.
  3. Pick the faction in the list.
  4. Adjust the colours (or hit Restore Vanilla Values to baseline first).
  5. OK — RPFM writes the new values into the appropriate DB row in your Pack, creating it as an override if it didn’t exist already.

Edits are reflected immediately in any open DB editor for the affected tables (the tool tracks which paths it touched and reloads any matching open views after save).

Per-game caveats

Different games store faction colours in different tables. Faction Painter resolves the right table and column names per game from its game-config map, so the picker UI stays the same across games even when the underlying schema doesn’t.

Unit Editor

The Unit Editor is a higher-level alternative to editing units row-by-row across main_units_tables, land_units_tables, unit_variants_tables and the loc strings that name them. It gives you a single pane that pulls in the relevant rows from each table and presents the unit as a coherent thing.

Disabled by default. The Unit Editor lives behind the Enable Unit Editor debug toggle in PackFile → Settings → Debug. Turn it on first; the menu entry is greyed out otherwise. Treat it as a power-user / experimental tool.

Warhammer 2 and Warhammer 3 only. The tool’s game-config map only covers WH2 and WH3 today. The menu entry is enabled for any game once the toggle is on, but trying to open it on an unsupported game will fail.

Open from Tools → Unit Editor with a Pack open (and the debug toggle enabled).

What it covers

For each unit, the editor surfaces:

  • The main_units_tables row.
  • The land_units_tables row.
  • The unit’s variant mesh references (unit_variants_tables etc., via the bundled variant editor).
  • Loc strings (display name, description, short description, and — for WH2 — strengths_weaknesses_texts).

Editing any field writes back into the right DB row when you save. Cloning a unit propagates the related rows so they stay consistent.

Not currently covered, despite what older docs claimed: unit_stats_*_tables, cost / upkeep / recruitment-time tables, and the unit caps tables. If you need to tweak those, edit the tables directly in the DB editor.

Per-game scope

Like Faction Painter, the Unit Editor reads game-specific table and column names from its game-config map. Only Warhammer 2 and Warhammer 3 are wired up today.

Limitations

  • Add / delete are limited. The primary “add” mechanism is Clone an existing unit; there’s no “delete unit” action.
  • For wholesale unit-creation workflows (full kit replacement, animations, sound, custom stats), the conventional table-by-table approach is still required.

Tip

Cloning an existing unit and tweaking it is the most reliable way to add a new unit through this editor. Start from a vanilla unit close to what you want and go from there.

Translating a mod

There are three approaches to translating a Total War mod. RPFM supports all of them, but the third — using the integrated Translator — is the recommended one.

The simple way

The one new modders tend to do, because it’s the easiest:

  1. Open the Pack.
  2. Open the Loc files.
  3. Manually translate the loc values.
  4. Save the Pack.

It’s not recommended. When the mod gets an update, you have to retranslate everything, or hunt down the new and changed lines and translate just those. Both flows are painful and error-prone, and you usually end up with a translation that “works but is partially outdated”.

The TSV way

The one people that have been hit by the simple-way problems tend to do:

  1. Open the Pack.
  2. Export every Loc to TSV.
  3. Use a translation tool to work on the TSV.
  4. Import the locs back.
  5. Save the Pack.

There are many variants of this — from “autotranslate everything with Google” (please proofread) to advanced workflows that isolate new / changed lines and only update those.

Translations are easier to update and easier to keep tidy in version control, but the workflow takes more setup time.

The new way: the Translator

RPFM ships with an integrated Translator that handles every painful part of the other two:

  1. Open the Pack you want to translate.
  2. Tools → Translator.
  3. Translate the lines marked Needs Retranslation.
  4. Hit Accept.
  5. Save the Pack.

The Translator does the heavy lifting for you and fixes problems that have historically plagued translations:

  • Detects lines unchanged from the vanilla English text and translates them automatically using the vanilla translation in the target language.
  • When translating a submod, reuses translations from parent mods if they exist.
  • When updating a translation, marks lines that have been removed, lines that have been altered, and lines that are new.
  • Saves the translation in a structured form that’s easy to share and collaborate on.

This means: no time wasted updating a translation (open the updated mod in the Translator and accept), no lines stuck on old translations after an update, no terminology drift across files.

How to use the Translator

The translator window has two halves.

Left side: the loc list

ColumnMeaning
KeyLoc key
Needs Retranslation?Checked when the line needs work — either it’s new, or the source text has changed since the previous translation.
RemovedThe line is no longer in the mod. The translator hides these by default.
Original ValueThe source text (usually English).
Translated ValueYour translation.

Right side: the editor

At the top: a short instruction line and the target language (picked automatically based on the language you have the game on).

Auto-translation behaviour — what should happen when you select a new line:

  • Auto-translate with DeepL — uses DeepL for high-quality machine translation. Requires a DeepL API key set in PackFile → Settings → AI.
  • Auto-translate with AI — sends the line to any AI service that exposes the OpenAI chat-completions wire format, with optional context. Quality depends on the model you pick. Requires an API URL, API key and model in the same AI pane. Examples of supported endpoints:
    • OpenAI: https://api.openai.com/v1/chat/completions
    • Anthropic (OpenAI compat): https://api.anthropic.com/v1/chat/completions
    • Gemini (OpenAI compat): https://generativelanguage.googleapis.com/v1beta/openai/chat/completions
    • OpenRouter: https://openrouter.ai/api/v1/chat/completions
    • Local (Ollama, vLLM, LM Studio, …): http://127.0.0.1:.../v1/chat/completions
  • Auto-translate with Google Translate — mediocre but free.
  • Copy Source Value — leaves the source text in place; useful for terms that don’t translate.
  • Empty Translated Value — does nothing on selection; you write the translation from scratch.

When you save the current line:

  • Replicate Edit in Pre-Translated Lines — also overwrite previously-translated lines that share the same source text.
  • Only Edit the Current Line — disable cross-line replication entirely.

Below the behaviour selectors: the Original Value (raw + formatted preview) and the Translated Value (raw + formatted preview, with prev/next buttons).

The pattern is: select a line, the right side loads it, you edit, you select the next line, repeat. When you’re done, Accept writes the translation to two places:

  • Inside the open Pack, as text/!!!!!!translated_locs.loc (or text/localisation.loc for older games), so the translation works in-game immediately.
  • On disk, as <RPFM config>/translations_local/<game>/<pack>/<SOURCE>-<LANGUAGE>.json (or <LANGUAGE>.json for format 0). This is the file you share.

Sharing a translation

Click Submit to the Translation Hub in the translator. RPFM saves the translation, shows you a summary to confirm, and opens a pull request on the Total War Translation Hub with it. The first time, it asks you to sign in with GitHub. See the Translator’s docs for the details.

Why share?

  • The Translator automatically searches the Hub for existing translations of mods you open. If someone translated this mod a year ago, you don’t start from scratch — you pick up where they left off.
  • Runcher (and any other launcher using TWPatcher) automatically pulls the Hub at launch and applies translations transparently — fixing the dreaded “no text when not English” bug along the way.

So sharing makes the translation future-proof and lets others use it without juggling extra packs or worrying about parent-mod compatibility.

Optimising a mod

Before you ship a release, optimise. RPFM’s optimiser strips dead weight from a Pack — duplicated rows, rows identical to vanilla, modding-tool byproducts, unused portrait variants — and the result is smaller, more compatible, and less likely to break neighbouring mods.

Why bother

  • Size. Removing dead rows shrinks the Pack — sometimes dramatically.
  • Compatibility. Duplicated and identical-to-vanilla rows tend to be left forgotten in mod packs (even when Diagnostics warns about them) and end up causing compatibility issues when another mod tries to modify them. Your mod accidentally overwrites changes for no reason.
  • Multi-language support. Identical-to-vanilla loc entries will overwrite the player’s localisation files if the player is not on English. So even though you’re not changing those lines, the optimiser-untouched Pack would silently turn them to English for non-English players. Bad.

How

Two right-click actions on the Pack root:

  • Optimize PackFile — opens the optimiser dialog, runs the steps you’ve ticked, applies the result to the in-memory Pack but doesn’t save. Lets you review the result before committing.
  • Save Pack For Release — opens the same optimiser dialog, runs it, and saves the result to disk. The shipping shortcut.

Both use the same OptimizerOptions under the hood. The dialog is where you fine-tune which steps run; your tick choices are persisted as defaults for the next invocation. There is no separate “Optimizer” pane in PackFile → Settings — the dialog itself is the configuration surface.

When

Right before you publish a release / update. Not earlier — the optimiser strips data, and that data is occasionally what you’re using as a reminder of intent during development.

What it actually does

The full step list lives in rpfm_extensions::optimizer, and the dialog groups them into four sections:

  • Pack — Remove ITM (identical-to-master) files, Apply compression, Encrypt the Pack (turns on index and data encryption; disabled for games whose Packs predate PFH4).
  • Table (DB / Loc) — Import datacores into twad_key_deletes (Warhammer 3+, see Datacores), Optimize datacored tables, Remove duplicated entries, Remove ITM rows, Remove ITNR (identical to new row) rows, Remove empty files.
  • Text — Remove unused .xml files in map/ folders, remove unused .xml files in the prefab folder, Remove .agf files, Remove .model_statistics files (the last two are BOB exporter byproducts).
  • Portrait Settings — Remove unused art sets, Remove unused variants, Remove empty masks, Remove empty files.

Each step can be ticked or un-ticked from the dialog. The defaults are conservative — destructive steps like “Optimize datacored tables” and “Remove unused art sets / variants” are off out of the box.

Datacores (twad_key_deletes)

A short tour of an old, painful pattern in Total War modding — and how Warhammer 3 (patch 6.3+) and RPFM let you stop using it.

What’s a datacore?

To explain what a datacore is, first we need to explain how table rows are loaded by the engine. Imagine the following table layout:

  • db/whatever_tables/@table — your high-priority overrides.
  • db/whatever_tables/ztable — your low-priority overrides.
  • db/whatever_tables/data__ — the vanilla table (filename varies per game).

When loading in-game, the three are merged. When two rows of the merged table have the same key, only the first one wins. So:

  • @table contains new rows and rows intended to overwrite vanilla. To change a vanilla unit’s stats, you put the modified row here.
  • ztable contains new rows you want overridden by other mods if they happen to clash (vanilla fixes, etc.).
  • data__ is the vanilla data.

Notice the gap: there’s no clean way to remove a vanilla row. To delete a building effect, the historical workaround was to import the whole vanilla table into your mod with the offending row removed, then ship that. That’s a datacore.

Why datacores are bad

Imagine you have a fancy effects table and you want to remove one of the effects in a submod. You import the table into your submod, edit it, and it works. Until… the parent mod is updated to add new effects. Now your submodded table doesn’t have those new effects, and either you update your table with them or your submod silently strips them.

And what if another submod tries to remove a different effect? Highlander situation: there can only be one.

In summary: datacoring is sometimes useful, but it reduces compatibility, makes the mod way bigger than it needs to be, and increases maintenance burden. Avoid unless absolutely needed.

For Warhammer 3 from patch 6.3 onwards, you don’t have to.

Datacores BEGONE: twad_key_deletes_tables

Patch 6.3 introduced a new table type, twad_key_deletes_tables, with a simple shape:

ColumnDescription
table_nameThe table to delete a row from.
keyThe row’s primary key (or composite key).

The engine reads this table at runtime and deletes those rows from the named tables — replacing the need for datacores entirely.

There are three ways to use it.

1. Migrate existing datacores

If your mod already contains datacored tables and you want to convert them:

  1. Right-click your Pack root → Optimize PackFile (or Save Pack For Release if you also want the optimiser to save the result).
  2. In the optimiser dialog, tick Import datacores into twad_key_deletes under the Table section.
  3. Accept.

RPFM analyses your datacored tables, finds the rows that are deleted relative to vanilla / parent, and adds them as entries to a new twad_key_deletes table. After running this, verify the new table contains the right keys, then delete the datacores.

Rename the generated table. RPFM will overwrite it if you re-run the optimiser. Pick a stable name like mymod_deletes.

2. Add deletes from the source table

The clean ergonomic flow when you’re removing entries you can see in another open table:

  1. Right-click your Pack → Create ▸ New DB, type twad_key_deletes_tables as the table name, and create it once. Don’t edit it manually.
  2. Open the source table (the one with the rows you want gone).
  3. Select the rows.
  4. Right-click → Add Selection to Key Deletes → pick your twad_key_deletes table.

RPFM adds the right keys to the deletes table for you. No manual key copying — and especially no risk of mistyping a multi-key composite, which is the easiest way to add an entry that silently does nothing. The submenu is only enabled when the active game is Warhammer 3 and the destination table exists in the open Pack.

3. Edit the table directly (last resort)

If neither of the above fits — for example, you want to delete a row that doesn’t exist in any open table you can right-click — you can edit twad_key_deletes like any other DB table. This is the highest risk path: bad keys silently do nothing in-game and RPFM can’t yet tell when you’ve fat-fingered a key. Use sparingly.

Caveats

A few things to keep in mind:

  • Removing a referenced row crashes the game. If you delete a land_units row that’s still referenced by units_to_groupings, the game will crash on load — same as if you’d deleted the row in a datacore. Worse, the engine’s error dialog tends to point at the wrong Pack, and RPFM’s diagnostics don’t yet validate twad_key_deletes references. If a vanilla row is referenced from elsewhere, plan accordingly.
  • Load order matters. If your high-priority mod removes a row and a lower-priority mod re-adds it (or vice versa), the engine will reflect the load order — the deletion gets undone.

These aren’t reasons to avoid twad_key_deletes; they’re reasons to think before removing rows. Compared to datacores, it’s still a massive ergonomic win.

Server overview

rpfm_server is a standalone backend process that exposes RPFM’s functionality over a local network protocol. The desktop UI (rpfm_ui) is one client of this server, but it isn’t the only one — anything that speaks JSON-RPC over WebSocket, or anything that speaks the Model Context Protocol, can drive the same backend.

This section is for tool authors and integrators. For end-user workflows, you don’t need anything here.

Architecture

┌──────────────────────┐    WebSocket    ┌──────────────────────┐
│       rpfm_ui        │ ◀────────────▶  │     rpfm_server      │
│  (Qt6 desktop app)   │                 │   (this binary)      │
└──────────────────────┘                 └──────────┬───────────┘
                                                    │
┌──────────────────────┐    HTTP/SSE                │
│   MCP clients (AI)   │ ◀──────────▶  /mcp ◀───────┘
└──────────────────────┘
  • The server binds to 127.0.0.1:45127 by default.
  • All clients talk to the same backend, but each connection has its own session (own open Packs, own dependency cache, own settings).
  • Behind the scenes the heavy lifting is in rpfm_lib and rpfm_extensions. The server is mostly an IPC gateway plus session bookkeeping.

What the server can do

Pretty much everything rpfm_ui can:

  • Open, save, close and inspect Packs.
  • Add, extract, rename, copy, delete, duplicate files.
  • Read and write every supported file type (DB tables, Loc, text, animations, …).
  • Run search, diagnostics, references against open Packs.
  • Manage dependencies, schemas, MyMods.
  • Drive the optimizer, the translator, startpos building, glTF export, etc.

The full surface is documented in Methods.

Endpoints

The server exposes three HTTP endpoints on 127.0.0.1:45127:

EndpointMethodPurpose
/wsGETWebSocket upgrade. Carries the JSON-RPC 2.0 protocol.
/sessionsGETREST: list every active session. Used by the UI session picker.
/versionGETREST: the server’s version and process ID. Used by the UI to reuse a running server.
/mcp*MCP StreamableHttpService. Each client gets its own session and McpServer.

Two ways in

You’ll typically pick one of two integration paths:

  • WebSocket — for full programmatic access. You write client code that sends JSON-RPC requests and matches their responses by ID. See WebSocket protocol.
  • MCP — for AI agents and other clients that already speak MCP. The server exposes most methods as tools, plus prompts and resources. See MCP interface.

Under the hood both pathways use the same Session abstraction. Each WebSocket connection gets its own isolated session with its own open Packs and dependency cache, and can reattach to it by passing its previous session_id back on the handshake. All MCP clients share one session, separate from the WebSocket ones. A WebSocket client and an MCP client running side by side don’t share state — they share the server process.

Spawning the server

rpfm_ui spawns rpfm_server automatically and the UI’s lifecycle owns the server’s. To run the server standalone (for tool development, automated tests, or to keep it warm between UI sessions):

./rpfm_server

The server logs to stderr and stays in the foreground until its last WebSocket session is gone, with no other session left. A server only used by MCP clients keeps running until stopped.

Where to next

Sessions & connection lifecycle

Each WebSocket connection lives inside a session, and all MCP clients share one more. A session owns a dedicated background thread that runs its requests serially against its in-memory state (open Packs, dependency cache, settings).

Sessions are isolated: open packs in one session aren’t visible in another. This is what makes “many UI clients talking to one server” safe.

Lifecycle

  1. Connect. A client opens /ws without a session ID. The server allocates a new SessionId, spins up a background thread, and immediately sends a session.connected notification so the client can stash the ID for later reconnection.

    { "jsonrpc": "2.0", "method": "session.connected", "params": { "session_id": 12345 } }
    
  2. Reconnect. A client opens /ws?session_id=12345. If the session still exists and isn’t shutting down, the new socket adopts it with all in-memory state preserved (open Packs, loaded dependencies, settings).

  3. Disconnect. When the WebSocket drops without a session.disconnect request, the session enters a 5-minute grace period (DEFAULT_SESSION_TIMEOUT_SECS = 300). Reconnecting cancels the timeout; otherwise the session and its background thread are torn down.

  4. Graceful disconnect. Call session.disconnect before closing your socket and the session is removed immediately, telemetry is flushed, and the background thread exits.

    { "jsonrpc": "2.0", "id": 99, "method": "session.disconnect" }
    
  5. Empty manager → process exit. When the last WebSocket session goes away and no other session is left, the rpfm_server process exits, so no orphaned server lingers in the background.

The MCP session

MCP clients have no disconnect signal the server can rely on, and newer MCP versions have no sessions at all, so all MCP clients share one session. It’s created by the first tool call, and removed after 5 minutes without tool calls, unless it has open packs or unfinished jobs. The next tool call creates a new one. Removing it never stops the server, so a server started by hand for MCP clients keeps running.

Autosaves

Every session autosaves its packs on its own, every autosave_interval minutes of its settings (0 disables them), keeping the newest autosave_amount autosaves of each pack. Only the packs changed since their last autosave or save are autosaved, and they’re written in the background, so requests don’t wait for them. Packs with autosaves disabled in their settings, and packs that aren’t mods or movies, are never autosaved. When the autosave folder grows past 25 GB, the server logs a warning.

Reconnection example

// First connect: store the session ID
const ws = new WebSocket("ws://127.0.0.1:45127/ws");
let sessionId: number | null = null;

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.method === "session.connected") {
    sessionId = msg.params.session_id;
    console.log("session", sessionId);
  }
};

// Later, after a network blip, reconnect with the stored ID:
const ws2 = new WebSocket(`ws://127.0.0.1:45127/ws?session_id=${sessionId}`);
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("ws://127.0.0.1:45127/ws"), CancellationToken.None);

int? sessionId = null;
// ... receive the session.connected notification and store sessionId ...

// Later, reconnect:
using var ws2 = new ClientWebSocket();
await ws2.ConnectAsync(
    new Uri($"ws://127.0.0.1:45127/ws?session_id={sessionId}"),
    CancellationToken.None);

Listing active sessions

The /sessions REST endpoint returns every live session as JSON:

curl http://127.0.0.1:45127/sessions
[
  {
    "session_id": 12345,
    "connection_count": 0,
    "timeout_remaining_secs": 180,
    "is_shutting_down": false,
    "pack_names": ["my_mod.pack"]
  },
  {
    "session_id": 12346,
    "connection_count": 1,
    "timeout_remaining_secs": null,
    "is_shutting_down": false,
    "pack_names": []
  }
]

Fields:

FieldDescription
session_idUnique identifier.
connection_countNumber of active WebSocket connections to this session.
timeout_remaining_secsSeconds until the session is cleaned up. Only set while disconnected.
is_shutting_downtrue when the session has been marked for shutdown.
pack_namesNames of the Packs currently open in this session.

The UI uses this endpoint to populate Pack → Select Session…, which lets a user reattach to a server session after the UI was killed or disconnected.

Per-session state

Each session has its own:

  • Open Packs (with their full in-memory contents and metadata).
  • Dependency cache view (game files, parent files, AK files).
  • Active game selection.
  • Settings: sessions start with the ones in RPFM’s settings.json, and clients can replace them with session.configure.
  • Jobs and their results.

When you call something like session.set_game, you’re changing it for your session, not globally.

WebSocket protocol

The /ws endpoint speaks JSON-RPC 2.0: every WebSocket text frame is one JSON-RPC request from the client, or one response or notification from the server. The methods themselves are listed in Methods.

Connecting

Open a WebSocket to ws://127.0.0.1:45127/ws. To reattach to an existing session, pass its ID: ws://127.0.0.1:45127/ws?session_id=12345 (see Sessions).

Right after connecting, the server sends a session.connected notification with the ID of your session:

{ "jsonrpc": "2.0", "method": "session.connected", "params": { "session_id": 12345 } }

Store it if you want to reconnect to the same session later.

Requests and responses

A request names a method and passes its params as an object:

{ "jsonrpc": "2.0", "id": 7, "method": "table.rows", "params": { "file": { "source": { "pack": "0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21" }, "path": "db/units_tables/my_units" }, "limit": 2 } }

The response carries the same id, and either a result or an error:

{ "jsonrpc": "2.0", "id": 7, "result": { "columns": ["key", "..."], "rows": [ ... ], "total": 51 } }

Use a different id for each request: responses arrive in the order requests finish, not the order they were sent. Requests of the same session still run in the order they arrived, one at a time, so a pack.save sent before a pack.close always saves the pack before closing it.

Methods without params can omit params.

Errors

Failed requests get an error with a code, a message, and a data object with the kind of error, so clients can match on it without parsing the message:

{ "jsonrpc": "2.0", "id": 8, "error": { "code": -32001, "message": "Pack not found: nope.pack", "data": { "kind": "pack_not_found", "details": "nope.pack" } } }
CodekindMeaning
-32001pack_not_foundNo open pack has that key.
-32002file_not_foundThe file doesn’t exist in its source.
-32003not_a_tableThe file isn’t a DB or Loc table.
-32004schema_not_loadedThe selected game has no schema loaded.
-32005dependencies_not_loadedThe dependencies of the selected game aren’t loaded.
-32006definition_not_foundThe schema has no definition for the table.
-32007read_onlyThe source can’t be edited, like the game files.
-32008job_not_foundNo job has that ID.
-32009diagnostics_not_rundiagnostics.list or diagnostics.report before any check.
-32010search_not_runsearch.matches, search.replace or search.report before any search.
-32011not_foundSomething the request names doesn’t exist.
-32601method_not_foundUnknown method.
-32602invalid_paramsThe params don’t match the method (including unknown params), or a value is invalid.
-32603internalAnything else.

Jobs

Methods that can take a long time (selecting a game, generating the dependencies cache, diagnostics, search, schema updates, the optimizer, Lua tests) run as jobs. They answer right away with the ID of their job:

{ "jsonrpc": "2.0", "id": 9, "result": { "job": 3 } }

The job then runs in order with the other requests of the session, and every change of its state is sent as a job.updated notification:

{ "jsonrpc": "2.0", "method": "job.updated", "params": { "job": 3, "method": "session.set_game", "state": "running", "stage": "Loading the schema and the dependencies" } }
{ "jsonrpc": "2.0", "method": "job.updated", "params": { "job": 3, "method": "session.set_game", "state": "finished", "result": { ... } } }

A job is queued, running (with an optional stage, and an optional progress from 0 to 100), finished (with the result of its method), failed (with its error) or cancelled. Instead of following the notifications, you can also call job.wait, which answers when the job ends or after a timeout, or job.status. Queued jobs can be cancelled with job.cancel; running ones can’t.

Diagnostics checks (diagnostics.run) don’t make other requests wait: a running check stops when another request arrives, and goes back to the queue behind it. A new check replaces the previous one if it hasn’t ended, which ends cancelled, and also checks what the replaced one would have checked. Follow the job of the last check you started to get its results.

Conventions

  • Pack keys. Open packs are identified by the key that pack.open, pack.new and session.status return: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. Keys never change while a pack is open, even when it’s saved under another name, and aren’t reused. A pack’s name is its file name, and its path is only set if it’s saved on disk. A method’s pack param is always the open pack it works on or changes. Where it’s optional, like in lookups and lists, setting it limits the method to that pack.
  • Sources. Files are found in a source: { "pack": "<key>" } for an open pack, or "game_files", "parent_files" or "assembly_kit" for the dependencies. A file is a { "source": ..., "path": ... } pair.
  • Reading and writing. Methods reading files take a source or a file, so they can read from anywhere. Methods changing files take a pack and a path, as only the files of open packs can change.
  • Paths. Paths inside packs use /. Where a method takes files or folders, a path is a file if one exists with that path, and a folder otherwise. Filters by path are called path_prefix. Paths on disk have names saying what they are, like destination or tsv_path.
  • Table names. Tables are named by their full name, like units_tables, in params (table_name) and in results, including the references between columns.
  • Pagination. Lists take an offset and an optional limit, and include the total amount of items, so you never get more than you asked for.
  • Settings. Sessions start with the settings in RPFM’s settings.json. Clients with their own settings, like the UI, send them with session.configure.

A complete round-trip

const ws = new WebSocket("ws://127.0.0.1:45127/ws");
let nextId = 1;
const pending = new Map<number, (message: any) => void>();

function call(method: string, params?: object): Promise<any> {
  const id = nextId++;
  ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
  return new Promise((resolve, reject) => pending.set(id, (message) => message.error ? reject(message.error) : resolve(message.result)));
}

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  if ("id" in message) {
    pending.get(message.id)?.(message);
    pending.delete(message.id);
  } else if (message.method === "session.connected") {
    console.log("session", message.params.session_id);
  }
};

ws.onopen = async () => {
  const pack = await call("pack.open", { paths: ["/path/to/my_mod.pack"] });
  const files = await call("files.list", { source: { pack: pack.key }, path_prefix: "db/", limit: 20 });
  console.log(files.total, files.files);
};
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri("ws://127.0.0.1:45127/ws"), CancellationToken.None);

var request = JsonSerializer.Serialize(new {
    jsonrpc = "2.0",
    id = 1,
    method = "pack.open",
    @params = new { paths = new[] { "/path/to/my_mod.pack" } },
});

await ws.SendAsync(Encoding.UTF8.GetBytes(request), WebSocketMessageType.Text, true, CancellationToken.None);

// Read messages until the one with "id": 1 arrives. The first one is the session.connected notification.

Disconnecting cleanly

Call session.disconnect before closing the socket. The server removes your session right away instead of keeping it for 5 minutes, and exits if it was the last one:

{ "jsonrpc": "2.0", "id": 99, "method": "session.disconnect" }

What’s next

  • Methods — every method, with its request and response types.
  • Sessions — reconnection and session lifetime.
  • Client example — a complete client with typed helpers.

Methods

Every method of the API, grouped by domain. Each domain’s page lists the params and the result of its methods, generated from the request and response structs of rpfm_ipc::api. Methods marked as jobs answer right away with a job ID, and send their result when the job ends (see Jobs).

Methods meant for clients with their own views of the files, like the UI, return RPFM’s own types (decoded files, typed search matches, full diagnostics), shown as any in their pages. Simpler clients should prefer the paginated methods next to them, like table.rows, search.matches or diagnostics.list.

Session and dependencies

MethodDescription
session.statusReturns the selected game, what’s loaded for it, and the open packs.
session.configureReplaces the settings the session runs with.
session.disconnectTells the server the client is closing, so its WebSocket session is removed right away instead of waiting for the timeout. Only for WebSocket clients.
session.set_gameSelects a game, loading its schema and, optionally, its dependencies. Runs as a job.
dependencies.generate_cacheGenerates the dependencies cache of the selected game from its files and Assembly Kit, and loads it. Runs as a job.
dependencies.rebuildReloads the dependencies of the selected game, like after changing the packs the open packs depend on. Runs as a job.
dependencies.tablesReturns the tables of the game files, and the startpos, twad and CEO tables of the schema, with their version.
dependencies.infoReturns the files of the dependencies of the selected game.
dependencies.table_dataReturns every decoded table of a type in the game files and the parent packs.

Packs

MethodDescription
pack.infoReturns the details of an open pack.
pack.newCreates a new empty pack. It has no path on disk until it’s saved with a path.
pack.openOpens one or more packs from disk, merged into a single one.
pack.open_vanillaOpens all the vanilla packs of the selected game, merged into a single one.
pack.closeCloses an open pack, discarding unsaved changes.
pack.close_allCloses all open packs, discarding unsaved changes.
pack.saveSaves an open pack to disk.
pack.updateChanges properties of an open pack. Only the fields set are changed.
pack.settingsReturns the settings of an open pack.
pack.update_settingsChanges settings of an open pack. Only the keys set are changed.

Files

MethodDescription
files.listLists the files of a source, sorted by path.
files.createCreates a new empty file in an open pack.
files.add_from_diskAdds files and folders from disk to an open pack.
files.copyCopies files and folders from any source into an open pack, keeping their paths.
files.deleteDeletes files and folders from an open pack.
files.renameRenames or moves files and folders of an open pack.
files.duplicateCopies files of an open pack in the same pack, adding a number to their names.
files.extractExtracts files and folders of an open pack, the game files or the parent packs to disk.
file.readReturns the contents of a file of any source.
file.writeReplaces the contents of a file of an open pack.
animpack.listLists the files inside an AnimPack of any source.
animpack.addCopies files of an open pack into an AnimPack of an open pack.
animpack.extractCopies files of an AnimPack of any source into an open pack.
animpack.deleteDeletes files from an AnimPack of an open pack.
file.view_dataReturns a file decoded as a client opening it in a view needs it.
files.infoReturns the info of files of an open pack, like their original pack and last modification time.
files.pasteCopies or moves files and folders of open packs into a folder of an open pack.
files.from_all_sourcesReturns files and folders found in the open packs, the parent packs and the game files.
files.save_filesAdds files to an open pack, replacing the ones with the same path, optionally optimizing the pack after it.
file.open_externalExtracts a file of an open pack to a temporary folder, and opens it in the system’s default program for its type.
file.save_externalReplaces a file of an open pack with the file on disk an external program edited.

Tables

MethodDescription
table.infoReturns the definition and row count of a table.
table.rowsReturns rows of a table, optionally filtered and with only some columns.
schema.definitionReturns the columns of a table as defined in the schema.
table.editEdits rows of a DB or Loc table in an open pack.
table.column_valuesReturns the distinct values of a column of a table, sorted.
table.unused_numbersReturns numbers no row uses yet in an integer column of a table, for new rows needing unique ones, like ids.
table.mergeMerges tables of the same type of an open pack into a new one.
table.upgradeUpdates a table of an open pack to the version it has in the game files.
table.rename_keyChanges a value of a key column of a table in every table of an open pack, including the columns referencing it and the loc keys generated from it.
table.add_key_deletesAdds rows to a key deletes table of an open pack, to delete keys of a table in the game.
table.export_tsvWrites a table to a TSV file.
table.import_tsvReplaces a table of an open pack with the contents of a TSV file, keeping its GUID.

Schema

MethodDescription
schema.tablesLists the tables of the schema, with the versions it has definitions for.
schema.patch_columnChanges how the schema describes a column, with a local patch.
schema.remove_patchesRemoves the local patches of a table, or of one of its columns, and reloads the schema.
schema.updateDownloads the latest schemas, reloads the selected game’s one, and rebuilds the dependencies. Runs as a job.
schema.update_from_assembly_kitUpdates the schema of the selected game with the tables of its Assembly Kit, and saves it. Runs as a job.
schema.raw_definitionsReturns definitions of a table as the schema stores them, to edit them.
schema.set_definitionAdds a definition to the schema of the selected game, or replaces the one with its version, then saves and reloads the schema.
schema.delete_definitionRemoves a definition from the schema of the selected game, then saves and reloads the schema.
schema.referencing_columnsReturns the columns of other tables referencing each column of a table.
schema.patchesReturns the patches applied to the columns of a table.
schema.import_patchesAdds patches to the schema of the selected game itself, and saves it.
schema.missing_definitionsReturns the tables of an open pack with rows that can’t be decoded with the schema.

References

MethodDescription
references.definitionFinds the row where a value of a referenced table is defined.
references.usagesFinds the rows of other tables referencing a value of a table.
references.locFinds the row of a Loc file with a key.
references.loc_sourceReturns the table, column and row a loc key belongs to.
references.row_locsReturns the loc entries of a row of a DB table: the loc key of each of its localised columns, with its text.
references.table_dataReturns the values each column of a table can reference, with their lookups.
references.valuesReturns the values a reference column of a table can have, with their display text.

Diagnostics

MethodDescription
diagnostics.runChecks the open packs for problems, keeping the results for diagnostics.list. Runs as a job.
diagnostics.listReturns results of the last check, optionally filtered.
diagnostics.reportReturns the results of the last diagnostics check, as the check returns them.
diagnostics.ignoreMakes the next checks of a pack skip some results. Saved in the pack’s settings.
MethodDescription
search.reportReturns the last search with all its matches, as the search returns them.
search.runSearches text, keeping the matches for search.matches. Runs as a job.
search.matchesReturns matches of the last search, optionally filtered.
search.replaceReplaces matches of the last search, and searches the edited files again.

Notes

MethodDescription
notes.listReturns the notes of an open pack for a path.
notes.addAttaches a note to a file or folder of an open pack.
notes.deleteDeletes a note of an open pack.

Tools

MethodDescription
tools.optimizer_optionsReturns the optimizer options, as the server’s settings have them.
tools.optimizeRemoves data of an open pack that’s identical to the vanilla one, or unneeded. Runs as a job.
tools.patch_siege_aiPatches the siege maps of an open pack, so the AI can use them. Warhammer games only.
tools.pack_mapAdds the tiles and tile maps of a map exported by Terry to an open pack.
tools.generate_missing_locsAdds empty loc entries for the localised columns of the tables of the open packs that have none.
tools.update_anim_idsOffsets the animation ids of an open pack, like after a game update moves them.
tools.anims_by_skeletonReturns the paths of the animations using a skeleton, in the open packs and the dependencies.
tools.export_gltfExports a RigidModel to a glTF file, with its textures.
tools.set_video_formatChanges the format of a ca_vp8 video of an open pack.
tools.live_exportExports the scripts and UI files of an open pack to the game’s data folder, to test them without saving the pack.
tools.init_mymodCreates the folder of a new MyMod, with editor configs for Lua scripting.
lua.run_testsRuns Lua tests against the scripts of the game and the open packs, outside of the game. Runs as a job.
startpos.campaignsReturns the campaigns a startpos can be built for.
startpos.startPrepares the Assembly Kit to build a startpos with the tables of an open pack, and launches the game to build it.
startpos.finishImports the startpos built by the game into the pack, or cancels the build, and cleans up the build files.
startpos.validateChecks the tables a campaign’s startpos is built from, and reports what would make the build fail, hang, or drop data. Works for both ways of building startposes. Runs as a job.
startpos.generateGenerates the startpos of a campaign from the tables of an open pack and the game’s files, and adds it to the pack as campaigns/<campaign>/startpos.esf. Unlike startpos.start, it doesn’t launch the game. Runs as a job.
startpos.extract_tablesExtracts the start_pos_* tables of a campaign from its startpos, the pack’s or the game’s if the pack has none, and adds them to the pack as db/<table>/<campaign>_startpos, with the victory objectives the startpos has as db/victory_objectives.txt (replacing the pack’s). The inverse of startpos.generate. Runs as a job.
startpos.compare_tablesExtracts the start_pos_* tables of a campaign from its startpos, like startpos.extract_tables, and compares them with the tables of the pack (and the game) the startpos is built from, to find what the build loses or changes. Rows are matched by what they describe, not by their ids. Runs as a job.
startpos.keep_campaignRemoves the rows of the pack’s start_pos_* tables that don’t belong to a campaign, as the Assembly Kit does before building a startpos, so tables holding several campaigns don’t break the build. Tables left without rows are deleted.
tools.plugin_scriptsReturns the plugin scripts in the scripts folder of the config.
tools.run_plugin_scriptRuns a plugin script over files of an open pack, reading back the files it changes.
lua.hoversReturns the docs of the game’s Lua API for the symbols of a Lua script, to show on hover.
ceo.traitsReturns the trait CEOs of the Assembly Kit tables.
ceo.add_entriesAdds CEO entries (armour, career, traits and their locs) to an open pack.
ceo.buildBuilds ceo_data.ccd in the Assembly Kit from the CEO tables of an open pack, running BOB.
ceo.importImports the ceo_data.ccd built by ceo.build into an open pack.

Translations

MethodDescription
translations.packReturns the whole translation of an open pack to a language, as the translator works with it.
translations.submitSubmits the saved translation of a pack to the Translation Hub, opening or updating a pull request.
translations.listReturns the translation of the texts of an open pack to a language.
translations.generate_vanillaGenerates the vanilla texts of a language from the game’s locale packs, so translations to and from it can reuse them.

GitHub

MethodDescription
github.sign_in_startStarts a GitHub sign-in, returning the code the user has to enter on GitHub.
github.sign_in_pollChecks if the user approved a sign-in. Once approved, the server keeps the sign-in.
github.accountReturns the GitHub account the user is signed in as.
github.sign_outSigns out of GitHub, deleting the stored sign-in.

Updates

MethodDescription
updates.checkChecks if there is an update.
updates.applyDownloads an update. Schemas can’t be updated with this, as they need reloading: use schema.update.

Jobs

MethodDescription
job.statusReturns the state of a job.
job.waitWaits for a job to end, and returns its state.
job.cancelCancels a job that hasn’t started yet. Running jobs can’t be cancelled.

Session and dependencies

Methods about the state of the session.

session.status

Returns the selected game, what’s loaded for it, and the open packs.

Params (GetSessionStatus)

Empty object: {}.

Result (SessionStatus)

NameTypeRequiredDescription
gamestringyesKey of the selected game, like warhammer_3.
schema_loadedbooleanyesIf a schema is loaded for the selected game. Tables can’t be read without one.
dependenciesDependenciesStatusyesWhat’s loaded of the dependencies of the selected game.
packsarray of PackSummaryyesThe open packs.

session.configure

Replaces the settings the session runs with.

Sessions start with the settings in the settings file. Clients owning their own settings send them with this before anything else, and again whenever they change.

Params (Configure)

NameTypeRequiredDescription
settingsanyyesThe settings.

Result (Done)

Empty object: {}.

session.disconnect

Tells the server the client is closing, so its WebSocket session is removed right away instead of waiting for the timeout. Only for WebSocket clients.

Params (Disconnect)

Empty object: {}.

Result (Done)

Empty object: {}.

session.set_game

Selects a game, loading its schema and, optionally, its dependencies.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (SetGame)

NameTypeRequiredDescription
gamestringyesKey of the game, like warhammer_3.
rebuild_dependenciesbooleanno, default trueIf the dependencies (vanilla files, Assembly Kit tables, parent packs) are loaded for the game.

Result (SessionStatus)

NameTypeRequiredDescription
gamestringyesKey of the selected game, like warhammer_3.
schema_loadedbooleanyesIf a schema is loaded for the selected game. Tables can’t be read without one.
dependenciesDependenciesStatusyesWhat’s loaded of the dependencies of the selected game.
packsarray of PackSummaryyesThe open packs.

dependencies.generate_cache

Generates the dependencies cache of the selected game from its files and Assembly Kit, and loads it.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (GenerateDependenciesCache)

NameTypeRequiredDescription
ignore_game_files_in_assembly_kitboolean or nullnoIf tables in the game files are skipped when reading the Assembly Kit. Defaults to the server’s setting.

Result (SessionStatus)

NameTypeRequiredDescription
gamestringyesKey of the selected game, like warhammer_3.
schema_loadedbooleanyesIf a schema is loaded for the selected game. Tables can’t be read without one.
dependenciesDependenciesStatusyesWhat’s loaded of the dependencies of the selected game.
packsarray of PackSummaryyesThe open packs.

dependencies.rebuild

Reloads the dependencies of the selected game, like after changing the packs the open packs depend on.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (RebuildDependencies)

NameTypeRequiredDescription
only_parent_packsbooleanno, default falseIf only the parent packs are reloaded, instead of the whole dependencies.

Result (SessionStatus)

NameTypeRequiredDescription
gamestringyesKey of the selected game, like warhammer_3.
schema_loadedbooleanyesIf a schema is loaded for the selected game. Tables can’t be read without one.
dependenciesDependenciesStatusyesWhat’s loaded of the dependencies of the selected game.
packsarray of PackSummaryyesThe open packs.

dependencies.tables

Returns the tables of the game files, and the startpos, twad and CEO tables of the schema, with their version.

Params (ListDependencyTables)

Empty object: {}.

Result (DependencyTables)

NameTypeRequiredDescription
tablesmap of integeryesVersion of each table, by table name. Empty if the dependencies are not loaded.

dependencies.info

Returns the files of the dependencies of the selected game.

Meant for clients showing the dependencies as a tree. Others should use files.list.

Params (GetDependenciesInfo)

Empty object: {}.

Result (DependenciesInfo)

One of RPFM’s own types, without a schema. See its docs in rpfm_lib or rpfm_extensions.

dependencies.table_data

Returns every decoded table of a type in the game files and the parent packs.

Meant for clients comparing whole tables. Others should use table.rows.

Params (GetDependencyTableData)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.

Result (DependencyTableData)

NameTypeRequiredDescription
tablesarray of anyyesThe tables.

Types

Type DependenciesStatus

What’s loaded of the dependencies of the selected game.

NameTypeRequiredDescription
vanilla_loadedbooleanyesIf the vanilla files of the game are loaded.
assembly_kit_loadedbooleanyesIf the tables of the game’s Assembly Kit are loaded.
parent_filesintegeryesAmount of files loaded from the parent packs of the open packs.
errorstring or nullnoWhy the dependencies couldn’t be fully loaded, like a missing or outdated dependencies cache, if they couldn’t.

Type PackSummary

Short description of an open pack.

NameTypeRequiredDescription
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pathstring or nullyesPath of the pack on disk, if it’s saved there.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
file_countintegeryesAmount of files in the pack.

Packs

Methods about the open packs.

pack.info

Returns the details of an open pack.

Params (GetPackInfo)

NameTypeRequiredDescription
packstringyesKey of the pack.

Result (PackDetails)

NameTypeRequiredDescription
versionstringyesVersion of the pack format, like PFH5.
compressionstringyesCompression format of the files of the pack, like None or Lz4.
index_encryptedbooleanyesIf the index of the pack is encrypted.
data_encryptedbooleanyesIf the data of the pack is encrypted.
index_includes_timestampbooleanyesIf the index of the pack includes the timestamp of each file.
extended_headerbooleanyesIf the pack has the extended header of some Arena packs.
timestampintegeryesUnix time of the last time the pack was saved.
dependenciesarray of PackDependencyyesPacks this pack depends on, loaded as parent files.
operational_modeanyyesIf the pack is in normal mode ("Normal") or MyMod mode ({"MyMod": [game_folder, mod_name]}).
file_countintegeryesAmount of files in the pack.
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
pathstring or nullyesPath of the pack on disk, if it’s saved there.

pack.new

Creates a new empty pack. It has no path on disk until it’s saved with a path.

Params (NewPack)

NameTypeRequiredDescription
namestring or nullnoFile name of the pack, like my_mod.pack. Defaults to new_pack.pack, numbered if there’s already an open pack with that name.

Result (PackSummary)

NameTypeRequiredDescription
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pathstring or nullyesPath of the pack on disk, if it’s saved there.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
file_countintegeryesAmount of files in the pack.

pack.open

Opens one or more packs from disk, merged into a single one.

Params (OpenPack)

NameTypeRequiredDescription
pathsarray of stringyesPaths of the packs on disk. If there are more than one, the merged pack has no path, and is named after the first one.
lazy_loadingboolean or nullnoIf file data is read from disk only when needed. Defaults to the server’s setting.

Result (PackSummary)

NameTypeRequiredDescription
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pathstring or nullyesPath of the pack on disk, if it’s saved there.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
file_countintegeryesAmount of files in the pack.

pack.open_vanilla

Opens all the vanilla packs of the selected game, merged into a single one.

Params (OpenVanillaPacks)

Empty object: {}.

Result (PackSummary)

NameTypeRequiredDescription
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pathstring or nullyesPath of the pack on disk, if it’s saved there.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
file_countintegeryesAmount of files in the pack.

pack.close

Closes an open pack, discarding unsaved changes.

Params (ClosePack)

NameTypeRequiredDescription
packstringyesKey of the pack.

Result (Done)

Empty object: {}.

pack.close_all

Closes all open packs, discarding unsaved changes.

Params (CloseAllPacks)

Empty object: {}.

Result (Done)

Empty object: {}.

pack.save

Saves an open pack to disk.

Params (SavePack)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstring or nullnoAbsolute path to save the pack to, creating its missing folders. If not set, the pack is saved to its current path.
cleanbooleanno, default falseIf files that failed to decode are removed before saving. Use it if saving normally fails.
disable_uuid_regenerationboolean or nullnoIf tables keep their GUID instead of getting a new one. Defaults to the server’s setting.
allow_editing_ca_packsboolean or nullnoIf packs of the vanilla types (not Mod or Movie) can be saved. Defaults to the server’s setting.

Result (PackSummary)

NameTypeRequiredDescription
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pathstring or nullyesPath of the pack on disk, if it’s saved there.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
file_countintegeryesAmount of files in the pack.

pack.update

Changes properties of an open pack. Only the fields set are changed.

Params (UpdatePack)

NameTypeRequiredDescription
packstringyesKey of the pack.
pack_typestring or nullnoNew type of the pack: Boot, Release, Patch, Mod or Movie.
compressionstring or nullnoNew compression format of the pack, like None or Lz4. If the game doesn’t support it, a supported one is used.
index_encryptedboolean or nullnoIf the index of the pack is encrypted. Only PFH4 and newer packs support encryption.
data_encryptedboolean or nullnoIf the data of the pack is encrypted. Only PFH4 and newer packs support encryption.
index_includes_timestampboolean or nullnoIf the index of the pack includes the timestamp of each file.
dependenciesarray of PackDependency or nullnoNew list of packs this pack depends on.
operational_modeanynoNew mode of the pack: "Normal", or {"MyMod": [game_folder, mod_name]}.

Result (PackDetails)

NameTypeRequiredDescription
versionstringyesVersion of the pack format, like PFH5.
compressionstringyesCompression format of the files of the pack, like None or Lz4.
index_encryptedbooleanyesIf the index of the pack is encrypted.
data_encryptedbooleanyesIf the data of the pack is encrypted.
index_includes_timestampbooleanyesIf the index of the pack includes the timestamp of each file.
extended_headerbooleanyesIf the pack has the extended header of some Arena packs.
timestampintegeryesUnix time of the last time the pack was saved.
dependenciesarray of PackDependencyyesPacks this pack depends on, loaded as parent files.
operational_modeanyyesIf the pack is in normal mode ("Normal") or MyMod mode ({"MyMod": [game_folder, mod_name]}).
file_countintegeryesAmount of files in the pack.
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
pathstring or nullyesPath of the pack on disk, if it’s saved there.

pack.settings

Returns the settings of an open pack.

Params (GetPackSettings)

NameTypeRequiredDescription
packstringyesKey of the pack.

Result (PackSettingsValues)

NameTypeRequiredDescription
textmap of stringno, default {}Multi-line text settings.
stringmap of stringno, default {}Single-line text settings.
boolmap of booleanno, default {}Boolean settings.
numbermap of integerno, default {}Number settings.

pack.update_settings

Changes settings of an open pack. Only the keys set are changed.

Params (UpdatePackSettings)

NameTypeRequiredDescription
packstringyesKey of the pack.
settingsPackSettingsValuesyesSettings to set, in the format pack.settings returns them.

Result (PackSettingsValues)

NameTypeRequiredDescription
textmap of stringno, default {}Multi-line text settings.
stringmap of stringno, default {}Single-line text settings.
boolmap of booleanno, default {}Boolean settings.
numbermap of integerno, default {}Number settings.

Types

Type PackDependency

A pack another pack depends on.

NameTypeRequiredDescription
enabledbooleanyesIf the dependency is loaded.
namestringyesFile name of the pack.

Type PackSettingsValues

Settings of a pack, by type of value and key, like diagnostics_files_to_ignore or disable_autosaves.

NameTypeRequiredDescription
textmap of stringno, default {}Multi-line text settings.
stringmap of stringno, default {}Single-line text settings.
boolmap of booleanno, default {}Boolean settings.
numbermap of integerno, default {}Number settings.

Files

Methods about the files of the open packs and the dependencies.

files.list

Lists the files of a source, sorted by path.

Params (ListFiles)

NameTypeRequiredDescription
sourceFileSourceyesSource to list.
path_prefixstringno, default ""Only files whose path starts with this are listed.
recursivebooleanno, default trueIf files in subfolders are listed. If false, the subfolders are listed instead.
file_typesarray of string or nullnoIf set, only files of these types are listed, like DB, Loc, Text, Image or RigidModel.
offsetintegerno, default 0Amount of files to skip.
limitinteger or nullnoMaximum amount of files to return. Defaults to 500.

Result (FileList)

NameTypeRequiredDescription
filesarray of FileEntryyesThe files in the page.
foldersarray of stringyesSubfolders directly under the prefix, with their full path. Only filled in non-recursive listings.
totalintegeryesAmount of files matching the request, in all pages.

files.create

Creates a new empty file in an open pack.

Params (CreateFile)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the new file in the pack, like db/units_tables/my_mod or text/db/my_mod.loc.
kindNewFileKindyesType of the new file.

Result (FileEntry)

NameTypeRequiredDescription
pathstringyesPath of the file in its source.
file_typestringyesType of the file, like DB, Loc, Text or Image.

files.add_from_disk

Adds files and folders from disk to an open pack.

Params (AddFilesFromDisk)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathsarray of stringyesPaths on disk of the files and folders to add.
folderstringno, default ""Folder of the pack to add them to. Empty for the root of the pack.
pack_pathsarray of string or nullnoPath in the pack of each entry of paths, in the same order, instead of putting them all in folder. For files, it’s their new path. For folders, it’s the folder their contents go to.
include_base_folderboolean or nullnoIf added folders keep their own name in the pack, instead of adding only their contents. Defaults to the server’s setting.
ignorearray of stringno, default []Files and folders under any of these paths are skipped.

Result (FilesAdded)

NameTypeRequiredDescription
addedarray of stringyesPaths in the pack of the added files.
not_addedarray of stringnoPaths of the files that couldn’t be added.
errorstring or nullnoLast error found while adding the files, if any.

files.copy

Copies files and folders from any source into an open pack, keeping their paths.

Params (CopyFiles)

NameTypeRequiredDescription
sourceFileSourceyesWhere to copy the files from.
pathsarray of stringyesPaths of the files and folders to copy.
packstringyesKey of the pack to copy them to.

Result (FilesAdded)

NameTypeRequiredDescription
addedarray of stringyesPaths in the pack of the added files.
not_addedarray of stringnoPaths of the files that couldn’t be added.
errorstring or nullnoLast error found while adding the files, if any.

files.delete

Deletes files and folders from an open pack.

Params (DeleteFiles)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathsarray of stringyesPaths of the files and folders to delete.

Result (FilesDeleted)

NameTypeRequiredDescription
deletedarray of stringyesPaths of the deleted files.

files.rename

Renames or moves files and folders of an open pack.

Params (RenameFiles)

NameTypeRequiredDescription
packstringyesKey of the pack.
renamesarray of FileRenameyesRenames to apply.

Result (FilesRenamed)

NameTypeRequiredDescription
renamedarray of FileRenameyesOld and new path of each renamed file.

files.duplicate

Copies files of an open pack in the same pack, adding a number to their names.

Params (DuplicateFiles)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathsarray of stringyesPaths of the files and folders to duplicate.

Result (FilesAdded)

NameTypeRequiredDescription
addedarray of stringyesPaths in the pack of the added files.
not_addedarray of stringnoPaths of the files that couldn’t be added.
errorstring or nullnoLast error found while adding the files, if any.

files.extract

Extracts files and folders of an open pack, the game files or the parent packs to disk.

Params (ExtractFiles)

NameTypeRequiredDescription
sourceFileSourceyesWhere the files are. The Assembly Kit tables can’t be extracted.
pathsarray of stringyesPaths of the files and folders to extract.
destinationstringyesFolder on disk to extract them to. Their paths in the source are kept under it.
as_tsvbooleanno, default falseIf tables are extracted as TSV files instead of binary ones.
tsv_keys_firstboolean or nullnoIf TSV files use the old column order, with keys first. Defaults to the server’s setting.

Result (FilesExtracted)

NameTypeRequiredDescription
extractedarray of stringyesPaths on disk of the extracted files.

file.read

Returns the contents of a file of any source.

Params (ReadFile)

NameTypeRequiredDescription
fileFileRefyesThe file.
formatReadFormatno, default "decoded"How to return the contents. Defaults to decoded.

Result (FileContents)

NameTypeRequiredDescription
file_typestringyesType of the file, like Text, Image or RigidModel.
contentsFileDatayesThe contents.

file.write

Replaces the contents of a file of an open pack.

Params (WriteFile)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the file. With raw contents, the file is created if it doesn’t exist.
contentsFileDatayesThe new contents. Text contents only work on files that are text files.

Result (Done)

Empty object: {}.

animpack.list

Lists the files inside an AnimPack of any source.

Params (ListAnimPack)

NameTypeRequiredDescription
fileFileRefyesThe AnimPack.

Result (FileList)

NameTypeRequiredDescription
filesarray of FileEntryyesThe files in the page.
foldersarray of stringyesSubfolders directly under the prefix, with their full path. Only filled in non-recursive listings.
totalintegeryesAmount of files matching the request, in all pages.

animpack.add

Copies files of an open pack into an AnimPack of an open pack.

Params (AddToAnimPack)

NameTypeRequiredDescription
packstringyesKey of the pack with the AnimPack.
animpackstringyesPath of the AnimPack.
source_packstringyesKey of the pack to copy the files from.
pathsarray of stringyesPaths of the files and folders to copy.

Result (FilesAdded)

NameTypeRequiredDescription
addedarray of stringyesPaths in the pack of the added files.
not_addedarray of stringnoPaths of the files that couldn’t be added.
errorstring or nullnoLast error found while adding the files, if any.

animpack.extract

Copies files of an AnimPack of any source into an open pack.

Params (ExtractFromAnimPack)

NameTypeRequiredDescription
fileFileRefyesThe AnimPack.
pathsarray of stringyesPaths inside the AnimPack of the files and folders to copy.
packstringyesKey of the pack to copy them to.

Result (FilesAdded)

NameTypeRequiredDescription
addedarray of stringyesPaths in the pack of the added files.
not_addedarray of stringnoPaths of the files that couldn’t be added.
errorstring or nullnoLast error found while adding the files, if any.

animpack.delete

Deletes files from an AnimPack of an open pack.

Params (DeleteFromAnimPack)

NameTypeRequiredDescription
packstringyesKey of the pack with the AnimPack.
animpackstringyesPath of the AnimPack.
pathsarray of stringyesPaths inside the AnimPack of the files and folders to delete.

Result (Done)

Empty object: {}.

file.view_data

Returns a file decoded as a client opening it in a view needs it.

Meant for clients with views for each type of file. Others should use file.read.

Params (GetViewData)

NameTypeRequiredDescription
fileFileRefyesThe file.

Result (ViewData)

One of:

  • { "Decoded": … }: The decoded file, and its info.
    • Decoded ([any, any], yes)
  • { "AnimPack": … }: The info of the files of an AnimPack, and of the AnimPack itself.
    • AnimPack ([any, any], yes)
  • { "Video": … }: The info of a video, without its frames, and of the file.
    • Video ([any, any], yes)
  • { "Notes": … }: The notes of a pack, as markdown.
    • Notes (any, yes)
  • "Unsupported": The file is of a type that is not decoded.

files.info

Returns the info of files of an open pack, like their original pack and last modification time.

Meant for clients showing the files of a pack as a tree. Others should use files.list.

Params (GetFilesInfo)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathsarray of string or nullnoPaths of the files. If not set, the info of every file of the pack is returned. Paths without a file are skipped.

Result (FilesInfo)

NameTypeRequiredDescription
filesarray of anyyesThe info of each file.

files.paste

Copies or moves files and folders of open packs into a folder of an open pack.

Each selected file or folder is pasted with its own name under the destination folder, not with its full path. Meant for clients with a clipboard. Others should use files.copy and files.rename.

Params (PasteFiles)

NameTypeRequiredDescription
sourcesmap of array of stringyesPaths of the files and folders to paste, by key of their pack.
cutbooleanyesIf the files are removed from their packs, instead of copied.
packstringyesKey of the pack to paste them into.
folderstringyesFolder of the pack to paste them into. Empty for the root of the pack.

Result (FilesPasted)

NameTypeRequiredDescription
addedarray of stringyesPaths of the files added to the target pack.
deletedmap of array of stringyesPaths of the files removed from each source pack, by pack key, if they were cut.
not_pastedarray of stringnoPaths of the files that couldn’t be pasted. Cut files that couldn’t be pasted are kept in their packs.
errorstring or nullnoLast error found while pasting the files, if any.

files.from_all_sources

Returns files and folders found in the open packs, the parent packs and the game files.

Meant for clients editing files of several sources at once, like the UI’s tools. Others should use file.read.

Params (GetFilesFromAllSources)

NameTypeRequiredDescription
pathsarray of anyyesPaths of the files and folders.
lowercase_pathsbooleanno, default falseIf the paths of the returned files are lowercased.

Result (FilesFromAllSources)

NameTypeRequiredDescription
filesanyyesThe files found, by path, for each source.

files.save_files

Adds files to an open pack, replacing the ones with the same path, optionally optimizing the pack after it.

Meant for clients editing files outside of the server, like the UI’s tools. Others should use file.write.

Params (SaveFiles)

NameTypeRequiredDescription
packstringyesKey of the pack.
filesarray of anyyesThe files, named with their path in the pack.
optimizebooleanno, default falseIf the pack is optimized with the optimizer options of the settings after adding the files.

Result (FilesChanged)

NameTypeRequiredDescription
addedarray of stringyesPaths of the added or edited files.
deletedarray of stringyesPaths of the deleted files.

file.open_external

Extracts a file of an open pack to a temporary folder, and opens it in the system’s default program for its type.

Params (OpenInExternalProgram)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the file.

Result (ExternalFile)

NameTypeRequiredDescription
pathstringyesPath of the extracted file on disk.

file.save_external

Replaces a file of an open pack with the file on disk an external program edited.

Params (SaveExternalFile)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the file in the pack.
external_pathstringyesPath of the edited file on disk.

Result (Done)

Empty object: {}.

Types

Type FileData

Contents of a file.

One of:

  • { "kind": "text", "text": … }: The text of a text file.
    • text (string, yes): The text.
  • { "kind": "decoded", "data": … }: The decoded file, as JSON, in the format file.read returns it.
    • data (any, yes): The decoded file.
  • { "kind": "raw", "base64": … }: The bytes of the file.
    • base64 (string, yes): The bytes, encoded in base64.

Type FileEntry

A file in a listing.

NameTypeRequiredDescription
pathstringyesPath of the file in its source.
file_typestringyesType of the file, like DB, Loc, Text or Image.

Type FileRef

A file in one of the sources.

NameTypeRequiredDescription
sourceFileSourceyesWhere the file is.
pathstringyesPath of the file in its source.

Type FileRename

A rename of a file or folder.

NameTypeRequiredDescription
fromstringyesCurrent path.
tostringyesNew path.

Type FileSource

Where a file is.

One of:

  • { "pack": … }: An open pack, by key.
    • pack (string, yes)
  • "game_files": The vanilla files of the selected game.
  • "parent_files": The files of the parent packs of the open packs.
  • "assembly_kit": The tables of the selected game’s Assembly Kit. Their paths are db/<table_name>/ak_data.

Type NewFileKind

Type of a new file.

One of:

  • { "type": "db", "table_name": …, "version"?: … }: An empty DB table.
    • table_name (string, yes): Name of the table, like units_tables.
    • version (integer or null, no): Version of the table. If not set, the version the table has in the game files is used, or the newest one if the game files are not loaded or don’t have the table.
  • { "type": "loc" }: An empty Loc table.
  • { "type": "text", "format"?: … }: An empty text file.
    • format (string or null, no): Format of the text, like Plain, Lua, Xml or Json. Defaults to Plain.
  • { "type": "anim_pack" }: An empty AnimPack.
  • { "type": "portrait_settings", "version": …, "copy_entries"?: … }: A portrait settings file, optionally with entries copied from the vanilla ones.
    • version (integer, yes): Version of the file format.
    • copy_entries (array of EntryCopy, no, default []): Vanilla entries to copy into the new file, with the ID each copy gets.
  • { "type": "vmd" }: An empty VMD (variant mesh definition) file.
  • { "type": "ws_model" }: An empty WSModel file.

Type EntryCopy

An entry copied from a vanilla file, with a new ID.

NameTypeRequiredDescription
fromstringyesID of the vanilla entry.
tostringyesID of the copy.

Type ReadFormat

How to return the contents of a file.

One of:

  • "decoded": The decoded file, as JSON. For DB and Loc tables, table.rows returns only the rows you need, and for images and other binary files raw is smaller.
  • "text": The text, for text files like scripts, XML or JSON files.
  • "raw": The bytes of the file, encoded in base64.

Tables

Methods to read DB and Loc tables, and their definitions.

Tables are described by their columns as rows see them: colour columns are merged, bitwise columns split and enum columns converted, so each row has one value per column.

table.info

Returns the definition and row count of a table.

Params (GetTableInfo)

NameTypeRequiredDescription
fileFileRefyesThe table.

Result (TableInfo)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables, or loc for Loc files.
versionintegeryesVersion of the table’s definition.
columnsarray of FieldyesColumns of the table, in the order row values are, with the schema patches applied.
row_countintegeryesAmount of rows in the table.

table.rows

Returns rows of a table, optionally filtered and with only some columns.

Params (GetTableRows)

NameTypeRequiredDescription
fileFileRefyesThe table.
columnsarray of string or nullnoIf set, only these columns are returned, in this order.
filtersarray of RowFilterno, default []Only rows matching all these filters are returned.
offsetintegerno, default 0Amount of matching rows to skip.
limitinteger or nullnoMaximum amount of rows to return. Defaults to 100.

Result (TableRows)

NameTypeRequiredDescription
columnsarray of stringyesNames of the columns of the values of each row.
rowsarray of TableRowyesThe rows in the page.
totalintegeryesAmount of rows matching the filters, in all pages.

schema.definition

Returns the columns of a table as defined in the schema.

Params (GetTableDefinition)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
versioninteger or nullnoVersion of the definition. If not set, the version the table has in the game files is used, or the newest one if the game files are not loaded or don’t have the table.

Result (TableDefinition)

NameTypeRequiredDescription
table_namestringyesName of the table.
versionintegeryesVersion of the definition.
columnsarray of FieldyesColumns of the table, in the order row values are, with the schema patches applied.

table.edit

Edits rows of a DB or Loc table in an open pack.

Edits are applied in order, each one on the result of the previous ones. If any of them fails, none is applied.

Params (EditTable)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the table in the pack.
editsarray of RowEdityesEdits to apply.

Result (TableEdited)

NameTypeRequiredDescription
row_countintegeryesAmount of rows the table has after the edits.

table.column_values

Returns the distinct values of a column of a table, sorted.

Params (GetColumnValues)

NameTypeRequiredDescription
table_namestringyesName of the table, like factions_tables.
columnstringyesName of the column.
include_packsbooleanno, default trueIf the tables of the open packs are included.
include_dependenciesbooleanno, default trueIf the tables of the game files and the parent packs are included.
prefixstringno, default ""Only values starting with this are returned.
offsetintegerno, default 0Amount of values to skip.
limitinteger or nullnoMaximum amount of values to return. Defaults to 500.

Result (ColumnValues)

NameTypeRequiredDescription
valuesarray of stringyesThe values in the page, sorted.
totalintegeryesAmount of values matching the request, in all pages.

table.unused_numbers

Returns numbers no row uses yet in an integer column of a table, for new rows needing unique ones, like ids.

Params (GetUnusedNumbers)

NameTypeRequiredDescription
table_namestringyesName of the table, like main_units_tables.
columnstringyesName of the integer column, like unique_index.
countintegerno, default 1Amount of numbers to return, up to 10000. Defaults to 1.

Result (UnusedNumbers)

NameTypeRequiredDescription
numbersarray of integeryesThe numbers, from lowest to highest, all above the highest one used in the open packs and the dependencies.

table.merge

Merges tables of the same type of an open pack into a new one.

Params (MergeTables)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathsarray of stringyesPaths of the tables to merge.
merged_pathstringyesPath of the merged table.
delete_sourcesbooleanno, default falseIf the merged tables are deleted afterwards.
deltabooleanno, default falseIf rows are merged by key against the vanilla data, instead of concatenated. Rows that can’t be reconciled automatically are returned as conflicts, and nothing is written.
resolutionsarray of anyno, default []How to resolve the conflicts of a previous delta merge.

Result (TablesMerged)

NameTypeRequiredDescription
mergedstring or nullyesPath of the merged table, if the merge was done.
conflictsarray of anynoRows that couldn’t be reconciled, if the merge wasn’t done. Pass resolutions for them to merge again.

table.upgrade

Updates a table of an open pack to the version it has in the game files.

Params (UpgradeTable)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the table.

Result (TableUpgraded)

NameTypeRequiredDescription
old_versionintegeryesVersion the table had.
new_versionintegeryesVersion the table has now.
deleted_columnsarray of stringyesColumns removed by the update.
added_columnsarray of stringyesColumns added by the update, with their default value.

table.rename_key

Changes a value of a key column of a table in every table of an open pack, including the columns referencing it and the loc keys generated from it.

Params (RenameKey)

NameTypeRequiredDescription
packstringyesKey of the pack.
table_namestringyesName of the table the key is from, like factions_tables.
columnstringyesName of the key column.
old_valuestringyesCurrent value.
new_valuestringyesNew value.
versioninteger or nullnoVersion of the table’s definition. Defaults to the version in the game files, or the newest one.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

table.add_key_deletes

Adds rows to a key deletes table of an open pack, to delete keys of a table in the game.

Params (AddKeyDeletes)

NameTypeRequiredDescription
packstringyesKey of the pack.
file_namestringyesFile name of the key deletes table, under db/twad_key_deletes_tables/.
table_namestringyesName of the table the keys belong to, like units_tables.
keysarray of stringyesKeys to delete.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

table.export_tsv

Writes a table to a TSV file.

Params (ExportTsv)

NameTypeRequiredDescription
fileFileRefyesThe table. Assembly Kit tables can’t be exported.
destinationstringyesPath of the TSV file to write.
tsv_keys_firstboolean or nullnoIf the TSV uses the old column order, with keys first. Defaults to the server’s setting.

Result (Done)

Empty object: {}.

table.import_tsv

Replaces a table of an open pack with the contents of a TSV file, keeping its GUID.

Params (ImportTsv)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the table in the pack.
tsv_pathstringyesPath of the TSV file to read.

Result (TableEdited)

NameTypeRequiredDescription
row_countintegeryesAmount of rows the table has after the edits.

Types

Type Field

A column of a table, as the schema describes it.

NameTypeRequiredDescription
namestringyesName of the column.
field_typeanyyesType of the values of the column, like StringU8, I32, F32, Boolean or ColourRGB. Sequences are an object with the definition of their rows, like { "SequenceU32": definition }.
is_keybooleanyesIf the column is part of the key of the table.
default_valuestring or nullyesValue of the column in new rows, if it has one.
is_filenamebooleanyesIf the column holds paths of files.
filename_relative_pathstring or nullyesFolders the files of the column are in, separated by ;. A % in them stands for the value of the column.
is_reference[string, string] or nullyesTable and column the values of this column must exist in, like ["factions", "key"]. The table name has no _tables suffix.
lookuparray of string or nullyesColumns of the referenced table shown next to the values of this one, to identify them.
descriptionstringyesDescription of the column. Empty if it has none.
ca_orderintegeryesPosition of the column in the Assembly Kit, or -1 if unknown.
is_bitwiseintegeryesIf more than 1, the column is one of the boolean columns a bitwise column was split into, named <column>_1 to <column>_<is_bitwise>.
enum_valuesmap of stringyesNames of the values of a column converted from an integer one, by value.
is_part_of_colourinteger or nullyesGroup of the colour columns merged into this one, if it’s a merged colour column.
unusedbooleanyesIf the game doesn’t use the column.

Type FileRef

A file in one of the sources.

NameTypeRequiredDescription
sourceFileSourceyesWhere the file is.
pathstringyesPath of the file in its source.

Type FileSource

Where a file is.

One of:

  • { "pack": … }: An open pack, by key.
    • pack (string, yes)
  • "game_files": The vanilla files of the selected game.
  • "parent_files": The files of the parent packs of the open packs.
  • "assembly_kit": The tables of the selected game’s Assembly Kit. Their paths are db/<table_name>/ak_data.

Type RowEdit

An edit of the rows of a table.

Values are given by column name, as booleans, numbers or strings, and converted to the type of their column.

One of:

  • { "op": "insert", "copy_from"?: …, "index"?: …, "values"?: … }: Adds a row. Columns not set get the value of the copied row, or their default value.
    • copy_from (RowRef or null, no): Row to copy, from any table with columns of the same names, like the vanilla one. Columns it doesn’t have get their default value.
    • index (integer or null, no): Position of the new row. If not set, it’s added at the end.
    • values (map of any, no, default {}): Values of the new row, by column name. They replace the ones of the copied row.
  • { "op": "update", "index": …, "values": … }: Changes values of a row.
    • index (integer, yes): Position of the row.
    • values (map of any, yes): New values, by column name. Columns not set keep their value.
  • { "op": "delete", "indexes": … }: Removes rows.
    • indexes (array of integer, yes): Positions of the rows to remove, as they’re before this edit.

Type RowFilter

A condition on the value of a column, compared as text.

NameTypeRequiredDescription
columnstringyesName of the column.
opFilterOpyesHow the value is compared.
valuestringyesValue to compare with.
ignore_casebooleanno, default falseIf the comparison ignores case.

Type FilterOp

Comparison of a row filter.

One of:

  • "equals": The value is the same as the filter’s.
  • "not_equals": The value is not the same as the filter’s.
  • "contains": The value contains the filter’s.
  • "starts_with": The value starts with the filter’s.
  • "ends_with": The value ends with the filter’s.

Type RowRef

A row of a table, in any source.

NameTypeRequiredDescription
fileFileRefyesThe table.
indexintegeryesIndex of the row in the table.

Type TableRow

A row of a table.

NameTypeRequiredDescription
indexintegeryesPosition of the row in the table. Used to edit it.
valuesarray of anyyesValues of the row, one per returned column: booleans, numbers or strings.

Schema

Methods about the schema of the selected game: its tables, local patches, and updates.

Definitions of single tables are read with GetTableDefinition.

schema.tables

Lists the tables of the schema, with the versions it has definitions for.

Params (ListSchemaTables)

NameTypeRequiredDescription
prefixstringno, default ""Only tables whose name starts with this are listed.

Result (SchemaTables)

NameTypeRequiredDescription
tablesmap of array of integeryesVersions with a definition, by table name, newest first.

schema.patch_column

Changes how the schema describes a column, with a local patch.

Local patches are kept apart from the schema, so they survive schema updates. The schema is reloaded after saving the patch, so it applies right away.

Params (PatchColumn)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
columnstringyesName of the column.
patchmap of stringyesValues to set, by patch key. The keys are description, is_key, default_value, is_filename, filename_relative_path, is_reference, lookup, lookup_hardcoded, not_empty and unused.

Result (Done)

Empty object: {}.

schema.remove_patches

Removes the local patches of a table, or of one of its columns, and reloads the schema.

Params (RemovePatches)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
columnstring or nullnoIf set, only the patches of this column are removed.

Result (Done)

Empty object: {}.

schema.update

Downloads the latest schemas, reloads the selected game’s one, and rebuilds the dependencies.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (UpdateSchemas)

Empty object: {}.

Result (SessionStatus)

NameTypeRequiredDescription
gamestringyesKey of the selected game, like warhammer_3.
schema_loadedbooleanyesIf a schema is loaded for the selected game. Tables can’t be read without one.
dependenciesDependenciesStatusyesWhat’s loaded of the dependencies of the selected game.
packsarray of PackSummaryyesThe open packs.

schema.update_from_assembly_kit

Updates the schema of the selected game with the tables of its Assembly Kit, and saves it.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (UpdateSchemaFromAssemblyKit)

NameTypeRequiredDescription
ignore_game_filesboolean or nullnoIf tables in the game files are skipped. Defaults to the server’s setting.

Result (SessionStatus)

NameTypeRequiredDescription
gamestringyesKey of the selected game, like warhammer_3.
schema_loadedbooleanyesIf a schema is loaded for the selected game. Tables can’t be read without one.
dependenciesDependenciesStatusyesWhat’s loaded of the dependencies of the selected game.
packsarray of PackSummaryyesThe open packs.

schema.raw_definitions

Returns definitions of a table as the schema stores them, to edit them.

Their fields are the raw on-disk layout, which can differ from the columns rows have (see schema.definition for those).

Params (GetRawDefinitions)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
versioninteger or nullnoIf set, only the definition of this version is returned.

Result (RawDefinitions)

NameTypeRequiredDescription
definitionsarray of anyyesThe definitions, newest first.

schema.set_definition

Adds a definition to the schema of the selected game, or replaces the one with its version, then saves and reloads the schema.

Params (SetDefinition)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
definitionanyyesThe definition, in the format schema.raw_definitions returns.

Result (Done)

Empty object: {}.

schema.delete_definition

Removes a definition from the schema of the selected game, then saves and reloads the schema.

Params (DeleteDefinition)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
versionintegeryesVersion of the definition.

Result (Done)

Empty object: {}.

schema.referencing_columns

Returns the columns of other tables referencing each column of a table.

Params (GetReferencingColumns)

NameTypeRequiredDescription
table_namestringyesName of the table, like factions_tables.
versioninteger or nullnoVersion of the table’s definition. Defaults to the version in the game files, or the newest one.

Result (ReferencingColumns)

NameTypeRequiredDescription
columnsmap of map of array of stringyesFor each referenced column, the referencing columns by table name.

schema.patches

Returns the patches applied to the columns of a table.

Params (GetTablePatches)

NameTypeRequiredDescription
table_namestringyesName of the table, like factions_tables.
versionintegeryesVersion of the table’s definition. If the table has no definition with this version, the patches of the table are returned.

Result (TablePatches)

NameTypeRequiredDescription
patchesmap of map of stringyesPatches by column name, as key-value pairs. See the valid keys in schema.patch_column.

schema.import_patches

Adds patches to the schema of the selected game itself, and saves it.

Unlike the ones of schema.patch_column, these patches are part of the schema, so a schema update replaces them.

Params (ImportPatches)

NameTypeRequiredDescription
patchesmap of map of map of stringyesPatches, by table name, column name and patch key. The keys are description, is_key, default_value, is_filename, filename_relative_path, is_reference, lookup, lookup_hardcoded, not_empty and unused.

Result (Done)

Empty object: {}.

schema.missing_definitions

Returns the tables of an open pack with rows that can’t be decoded with the schema.

Params (GetMissingDefinitions)

NameTypeRequiredDescription
packstringyesKey of the pack.

Result (MissingDefinitions)

NameTypeRequiredDescription
pathsarray of stringyesPaths of the tables, sorted.

Types

Type DependenciesStatus

What’s loaded of the dependencies of the selected game.

NameTypeRequiredDescription
vanilla_loadedbooleanyesIf the vanilla files of the game are loaded.
assembly_kit_loadedbooleanyesIf the tables of the game’s Assembly Kit are loaded.
parent_filesintegeryesAmount of files loaded from the parent packs of the open packs.
errorstring or nullnoWhy the dependencies couldn’t be fully loaded, like a missing or outdated dependencies cache, if they couldn’t.

Type PackSummary

Short description of an open pack.

NameTypeRequiredDescription
keystringyesKey identifying the pack in every method that works on it: a UUID, like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21. It never changes while the pack is open.
namestringyesFile name of the pack, like my_mod.pack.
pathstring or nullyesPath of the pack on disk, if it’s saved there.
pack_typestringyesType of the pack: Boot, Release, Patch, Mod or Movie.
file_countintegeryesAmount of files in the pack.

References

Methods to follow references between tables, and between tables and Loc files.

references.definition

Finds the row where a value of a referenced table is defined.

Searches the open packs, then the parent packs, the game files and the Assembly Kit tables, and returns the first row found.

Params (FindDefinition)

NameTypeRequiredDescription
table_namestringyesReferenced table, like factions_tables.
columnstringyesReferenced column. If it’s a localised column, the table’s key is searched instead.
valuestringyesValue to find.
packstring or nullnoIf set, only this open pack is searched, instead of all of them and the dependencies.

Result (FoundRow)

NameTypeRequiredDescription
valuesmap of anyyesValues of the row, by column name.
column_indexintegeryesIndex of the column the value is in.
pathstringyesPath of the table in its source.
row_indexintegeryesIndex of the row.
sourceFileSourceyesWhere the table is.

references.usages

Finds the rows of other tables referencing a value of a table.

The referencing columns are taken from the schema. Searches the open packs, the parent packs and the game files.

Params (FindUsages)

NameTypeRequiredDescription
table_namestringyesTable the value is from, like factions_tables.
columnstringyesColumn the value is from, like key.
valuestringyesValue to find usages of.
versioninteger or nullnoVersion of the table’s definition, to know which columns reference it. Defaults to the version in the game files, or the newest one.
packstring or nullnoIf set, only this open pack is searched, instead of all of them and the dependencies.
offsetintegerno, default 0Amount of usages to skip.
limitinteger or nullnoMaximum amount of usages to return. Defaults to 200.

Result (Usages)

NameTypeRequiredDescription
usagesarray of UsageyesThe usages in the page.
totalintegeryesAmount of usages, in all pages.

references.loc

Finds the row of a Loc file with a key.

Searches the open packs, then the parent packs and the game files, and returns the first row found.

Params (FindLoc)

NameTypeRequiredDescription
keystringyesKey to find, like factions_screen_name_wh_main_emp_empire.
packstring or nullnoIf set, only this open pack is searched, instead of all of them and the dependencies.

Result (FoundRow)

NameTypeRequiredDescription
valuesmap of anyyesValues of the row, by column name.
column_indexintegeryesIndex of the column the value is in.
pathstringyesPath of the table in its source.
row_indexintegeryesIndex of the row.
sourceFileSourceyesWhere the table is.

references.loc_source

Returns the table, column and row a loc key belongs to.

Params (GetLocSource)

NameTypeRequiredDescription
keystringyesThe loc key, like factions_screen_name_wh_main_emp_empire.

Result (LocSourceLookup)

NameTypeRequiredDescription
sourceLocSource or nullyesTable, column and row the key belongs to, if they were found.

references.row_locs

Returns the loc entries of a row of a DB table: the loc key of each of its localised columns, with its text.

Params (GetRowLocs)

NameTypeRequiredDescription
rowRowRefyesThe row.

Result (RowLocs)

NameTypeRequiredDescription
locsarray of RowLocyesThe loc entry of each localised column of the row.

references.table_data

Returns the values each column of a table can reference, with their lookups.

Meant for clients editing whole tables, to fill their dropdowns. Others should use references.values.

Params (GetTableReferenceData)

NameTypeRequiredDescription
table_namestringyesName of the table, like units_tables.
versionintegeryesVersion of the table’s definition.
regeneratebooleanno, default falseIf the references to the tables of the open packs are generated again, instead of reusing the cached ones.

Result (TableReferenceData)

NameTypeRequiredDescription
columnsmap of anyyesReferenceable values, by column index.

references.values

Returns the values a reference column of a table can have, with their display text.

The values come from the referenced column, in the open packs and the dependencies.

Params (GetReferenceValues)

NameTypeRequiredDescription
table_namestringyesName of the table with the reference column, like units_tables.
columnstringyesName of the reference column.
prefixstringno, default ""Only values starting with this are returned.
offsetintegerno, default 0Amount of values to skip.
limitinteger or nullnoMaximum amount of values to return. Defaults to 500.

Result (ReferenceValues)

NameTypeRequiredDescription
valuesarray of ReferenceValueyesThe values in the page, sorted.
totalintegeryesAmount of values matching the request, in all pages.

Types

Type FileSource

Where a file is.

One of:

  • { "pack": … }: An open pack, by key.
    • pack (string, yes)
  • "game_files": The vanilla files of the selected game.
  • "parent_files": The files of the parent packs of the open packs.
  • "assembly_kit": The tables of the selected game’s Assembly Kit. Their paths are db/<table_name>/ak_data.

Type LocSource

Table, column and row a loc key belongs to.

NameTypeRequiredDescription
table_namestringyesName of the table, like factions_tables.
columnstringyesName of the localised column.
key_valuesarray of stringyesValues of the key columns of the row.

Type ReferenceValue

A value a reference column can have.

NameTypeRequiredDescription
valuestringyesThe value.
lookupstringnoText shown next to the value, like the name of the referenced row. Empty if there’s none.

Type RowLoc

The loc entry of a localised column of a row.

NameTypeRequiredDescription
columnstringyesName of the localised column, like onscreen_name.
keystringyesLoc key of the column, like land_units_onscreen_name_my_unit.
textstring or nullyesText of the loc key, from the open packs or the dependencies, or null if there is none.

Type RowRef

A row of a table, in any source.

NameTypeRequiredDescription
fileFileRefyesThe table.
indexintegeryesIndex of the row in the table.

Type FileRef

A file in one of the sources.

NameTypeRequiredDescription
sourceFileSourceyesWhere the file is.
pathstringyesPath of the file in its source.

Type Usage

A row referencing a value.

NameTypeRequiredDescription
columnstringyesName of the column referencing the value.
valuesmap of anyyesValues of the row, by column name.
column_indexintegeryesIndex of the column the value is in.
pathstringyesPath of the table in its source.
row_indexintegeryesIndex of the row.
sourceFileSourceyesWhere the table is.

Diagnostics

Methods to check the open packs for problems.

The server keeps the results of the last check, so clients get a summary when running it, and read the results they need with ListDiagnostics.

diagnostics.run

Checks the open packs for problems, keeping the results for diagnostics.list.

A running check stops when other requests arrive, and is queued again behind them. A new check replaces the one that hasn’t ended yet, which ends cancelled, and also checks the paths the replaced one would have checked.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (RunDiagnostics)

NameTypeRequiredDescription
pathsarray of stringno, default []If set, only files and folders of the open packs at these paths are checked again, keeping the results of the last check for the rest. If not set, or if there are no previous results, everything is checked.
ignored_typesarray of stringno, default []Report types to skip, like OutdatedTable or InvalidReference.
check_assembly_kit_only_referencesbooleanno, default falseIf references to tables only in the Assembly Kit are checked too.

Result (DiagnosticsSummary)

NameTypeRequiredDescription
totalintegeryesAmount of results.
by_levelmap of integeryesAmount of results of each level.
by_typemap of integeryesAmount of results of each report type.

diagnostics.list

Returns results of the last check, optionally filtered.

Params (ListDiagnostics)

NameTypeRequiredDescription
levelsarray of DiagnosticLevel or nullnoIf set, only results of these levels are returned.
report_typesarray of string or nullnoIf set, only results of these report types are returned.
packstring or nullnoIf set, only results of this pack are returned.
path_prefixstringno, default ""Only results of files whose path starts with this are returned.
offsetintegerno, default 0Amount of matching results to skip.
limitinteger or nullnoMaximum amount of results to return. Defaults to 100.

Result (DiagnosticList)

NameTypeRequiredDescription
resultsarray of DiagnosticResultyesThe results in the page.
totalintegeryesAmount of results matching the request, in all pages.

diagnostics.report

Returns the results of the last diagnostics check, as the check returns them.

Meant for clients showing the results with all their details. Others should use diagnostics.list.

Params (GetDiagnosticsReport)

Empty object: {}.

Result (Diagnostics)

One of RPFM’s own types, without a schema. See its docs in rpfm_lib or rpfm_extensions.

diagnostics.ignore

Makes the next checks of a pack skip some results. Saved in the pack’s settings.

Params (IgnoreDiagnostics)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesFiles whose path starts with this are affected.
columnsarray of stringno, default []If set, only these columns of the files are affected.
report_typesarray of stringno, default []If set, only these report types are skipped. Otherwise, everything is skipped.

Result (Done)

Empty object: {}.

Types

Type DiagnosticLevel

Severity of a result.

One of:

  • "info": A suggestion, or information about the mod.
  • "warning": Something that may be a mistake, or cause problems in some situations.
  • "error": Something that will likely cause problems, like invalid references or malformed data.

Type DiagnosticResult

A problem found in a check.

NameTypeRequiredDescription
packstringyesKey of the pack with the problem. Empty for problems of the configuration.
pathstringyesPath of the file with the problem. Empty for problems of a whole pack or of the configuration.
checkedstringyesWhat was checked: db, loc, text, pack, config, dependency, portrait_settings, group_formations or anim_fragment_battle.
report_typestringyesType of the problem, like InvalidReference. Used to ignore it.
levelDiagnosticLevelyesSeverity of the problem.
messagestringyesDescription of the problem.
cellsarray of array of integernoTable cells with the problem, as [row, column]. -1 means the whole row or column.
columnsarray of stringnoNames of the columns of the cells with the problem.

Search

Methods to search text across the open packs, the dependencies and the schema, and replace it.

The server keeps the matches of the last search, so clients get a summary when running it, read the matches they need with ListSearchMatches, and replace them by ID.

search.report

Returns the last search with all its matches, as the search returns them.

Meant for clients showing the matches with all their details. Others should use search.matches.

Params (GetSearchReport)

Empty object: {}.

Result (SearchReport)

NameTypeRequiredDescription
searchanyyesThe search, with its matches.
first_idsmap of array of integeryesID of the first match of each file, by type of file, in the order of the files in the search. The other matches of a file follow it, so its match number n has the ID first + n.

search.run

Searches text, keeping the matches for search.matches.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (RunSearch)

NameTypeRequiredDescription
patternstringyesText to search, or a regular expression if use_regex is set.
case_sensitivebooleanno, default falseIf the search is case sensitive.
use_regexbooleanno, default falseIf the pattern is a regular expression.
sourcesarray of FileSourceyesWhere to search: open packs, the game files, the parent packs, or the Assembly Kit tables.
file_typesarray of string or nullnoTypes of files to search, named like in files.list: DB, Loc, Text, Atlas, PortraitSettings, RigidModel, UnitVariant, AnimFragmentBattle, binary types like Image, Audio or Unknown, or Schema for the column names of the schema. Defaults to DB, Loc and Text.

Result (SearchSummary)

NameTypeRequiredDescription
totalintegeryesAmount of matches.
filesintegeryesAmount of files with matches.
by_typemap of integeryesAmount of matches in each type of file.

search.matches

Returns matches of the last search, optionally filtered.

Params (ListSearchMatches)

NameTypeRequiredDescription
file_typesarray of string or nullnoIf set, only matches in these types of files are returned.
path_prefixstringno, default ""Only matches in files whose path starts with this are returned.
offsetintegerno, default 0Amount of matching matches to skip.
limitinteger or nullnoMaximum amount of matches to return. Defaults to 100.

Result (SearchMatchList)

NameTypeRequiredDescription
matchesarray of SearchMatchyesThe matches in the page.
totalintegeryesAmount of matches matching the request, in all pages.

search.replace

Replaces matches of the last search, and searches the edited files again.

Only matches in open packs can be replaced, and only in tables, text files, atlases, portrait settings, rigid models, unit variants and anim fragment battles.

Params (ReplaceSearchMatches)

NameTypeRequiredDescription
replace_textstringyesText to replace the matches with.
matchesarray of integer or nullnoIDs of the matches to replace. If not set, all the matches are replaced.

Result (SearchReplaced)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.
summarySearchSummaryyesSummary of the matches left after searching the edited files again.

Types

Type FileSource

Where a file is.

One of:

  • { "pack": … }: An open pack, by key.
    • pack (string, yes)
  • "game_files": The vanilla files of the selected game.
  • "parent_files": The files of the parent packs of the open packs.
  • "assembly_kit": The tables of the selected game’s Assembly Kit. Their paths are db/<table_name>/ak_data.

Type SearchMatch

A match of a search.

NameTypeRequiredDescription
idintegeryesID of the match, used to replace it. IDs change after every search and replace.
sourceFileSource or nullnoWhere the file with the match is. Not set for matches in the schema.
pathstringyesPath of the file with the match. Empty for matches in the schema.
file_typestringyesType of the file with the match, like DB or Text.
detailsanyyesWhere the match is in its file, and its text. The fields depend on the type of the file: tables have column_name, row_number, start, end and text, text files have row, start, end and text, and so on.

Type SearchSummary

Summary of the matches of a search.

NameTypeRequiredDescription
totalintegeryesAmount of matches.
filesintegeryesAmount of files with matches.
by_typemap of integeryesAmount of matches in each type of file.

Notes

Methods about the notes of the open packs: comments attached to their files and folders.

notes.list

Returns the notes of an open pack for a path.

Params (ListNotes)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringno, default ""Path of the file or folder whose notes to return, with the notes of the folders containing it. Empty for all the notes of the pack.

Result (NoteList)

NameTypeRequiredDescription
notesarray of NoteEntryyesThe notes.

notes.add

Attaches a note to a file or folder of an open pack.

Params (AddNote)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringno, default ""Path of the file or folder to attach the note to. Empty for the whole pack.
messagestringyesText of the note.
urlstring or nullnoLink to attach to the note.
idinteger or nullnoID of a note attached to the same path, to replace it instead of adding a new one.

Result (NoteEntry)

NameTypeRequiredDescription
idintegeryesID of the note, unique within its path.
pathstringyesPath of the file or folder the note is attached to. Empty for the whole pack.
messagestringyesText of the note.
urlstring or nullnoLink attached to the note, if any.

notes.delete

Deletes a note of an open pack.

Params (DeleteNote)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath the note is attached to.
idintegeryesID of the note.

Result (Done)

Empty object: {}.

Types

Type NoteEntry

A note of a pack.

NameTypeRequiredDescription
idintegeryesID of the note, unique within its path.
pathstringyesPath of the file or folder the note is attached to. Empty for the whole pack.
messagestringyesText of the note.
urlstring or nullnoLink attached to the note, if any.

Tools

Methods of the tools: optimizer, maps, startpos, animations, glTF export, MyMods and Lua tests.

tools.optimizer_options

Returns the optimizer options, as the server’s settings have them.

Params (GetOptimizerOptions)

Empty object: {}.

Result (OptimizerOptionValues)

NameTypeRequiredDescription
optionsmap of booleanyesIf each optimization is enabled, by name.

tools.optimize

Removes data of an open pack that’s identical to the vanilla one, or unneeded.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (OptimizePack)

NameTypeRequiredDescription
packstringyesKey of the pack.
optionsmap of booleanno, default {}Optimizations to enable or disable, by name. The rest keep the server’s settings. See tools.optimizer_options for the names.

Result (FilesChanged)

NameTypeRequiredDescription
addedarray of stringyesPaths of the added or edited files.
deletedarray of stringyesPaths of the deleted files.

tools.patch_siege_ai

Patches the siege maps of an open pack, so the AI can use them. Warhammer games only.

Params (PatchSiegeAi)

NameTypeRequiredDescription
packstringyesKey of the pack.

Result (SiegeAiPatched)

NameTypeRequiredDescription
messagestringyesDescription of what was patched.
deletedarray of stringyesPaths of the files deleted by the patch.

tools.pack_map

Adds the tiles and tile maps of a map exported by Terry to an open pack.

Params (PackMap)

NameTypeRequiredDescription
packstringyesKey of the pack.
tile_mapsarray of stringyesPaths on disk of the tile maps.
tilesarray of MapTileno, default []Tiles to add.

Result (FilesChanged)

NameTypeRequiredDescription
addedarray of stringyesPaths of the added or edited files.
deletedarray of stringyesPaths of the deleted files.

tools.generate_missing_locs

Adds empty loc entries for the localised columns of the tables of the open packs that have none.

Params (GenerateMissingLocs)

Empty object: {}.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

tools.update_anim_ids

Offsets the animation ids of an open pack, like after a game update moves them.

Params (UpdateAnimIds)

NameTypeRequiredDescription
packstringyesKey of the pack.
starting_idintegeryesOnly ids from this one are changed.
id_offsetintegeryesAmount to add to each id.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

tools.anims_by_skeleton

Returns the paths of the animations using a skeleton, in the open packs and the dependencies.

Params (AnimsBySkeleton)

NameTypeRequiredDescription
skeletonstringyesName of the skeleton.

Result (FilePaths)

NameTypeRequiredDescription
pathsarray of stringyesThe paths, sorted.

tools.export_gltf

Exports a RigidModel to a glTF file, with its textures.

Params (ExportGltf)

NameTypeRequiredDescription
fileFileRefyesThe RigidModel.
destinationstringyesPath of the glTF file to write.

Result (Done)

Empty object: {}.

tools.set_video_format

Changes the format of a ca_vp8 video of an open pack.

Params (SetVideoFormat)

NameTypeRequiredDescription
packstringyesKey of the pack.
pathstringyesPath of the video.
formatstringyesNew format: CaVp8 or Ivf.

Result (Done)

Empty object: {}.

tools.live_export

Exports the scripts and UI files of an open pack to the game’s data folder, to test them without saving the pack.

Params (LiveExport)

NameTypeRequiredDescription
packstringyesKey of the pack.

Result (Done)

Empty object: {}.

tools.init_mymod

Creates the folder of a new MyMod, with editor configs for Lua scripting.

Params (InitMyMod)

NameTypeRequiredDescription
game_folderstringyesFolder name of the game of the MyMod, like warhammer_3.
namestringyesName of the MyMod.
sublime_supportbooleanno, default falseIf a Sublime Text project is created.
vscode_supportbooleanno, default falseIf a VSCode config is created.
gitignorestring or nullnoIf set, a git repository is created with this .gitignore.

Result (MyModCreated)

NameTypeRequiredDescription
pack_pathstringyesPath of the pack of the MyMod. It doesn’t exist until it’s saved.

lua.run_tests

Runs Lua tests against the scripts of the game and the open packs, outside of the game.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (RunLuaTests)

NameTypeRequiredDescription
codestringyesCode of the Lua test file.
campaignstring or nullnoCampaign whose vanilla scripts to load, like main_warhammer. If not set, only the script libraries and the mods are loaded.

Result (LuaTestResults)

NameTypeRequiredDescription
reportanyyesThe report of the tests: passed and failed tests, with their errors and output.

startpos.campaigns

Returns the campaigns a startpos can be built for.

Params (GetStartposCampaigns)

NameTypeRequiredDescription
packstringyesKey of the pack with the startpos tables.

Result (StartposCampaigns)

NameTypeRequiredDescription
campaignsarray of stringyesCampaign keys, sorted.
last_usedstring or nullyesCampaign the last startpos of the pack was built for, if any.

startpos.start

Prepares the Assembly Kit to build a startpos with the tables of an open pack, and launches the game to build it.

When the game is closed, call startpos.finish to import the startpos into the pack.

Params (StartStartpos)

NameTypeRequiredDescription
packstringyesKey of the pack with the startpos tables.
campaignstringyesCampaign to build the startpos for.
process_hlp_spd_databooleanno, default falseIf the HLP and SPD data are built too.

Result (Done)

Empty object: {}.

startpos.finish

Imports the startpos built by the game into the pack, or cancels the build, and cleans up the build files.

Params (FinishStartpos)

NameTypeRequiredDescription
cancelbooleanno, default falseIf the build is cancelled instead of imported.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

startpos.validate

Checks the tables a campaign’s startpos is built from, and reports what would make the build fail, hang, or drop data. Works for both ways of building startposes.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (ValidateStartpos)

NameTypeRequiredDescription
packstringyesKey of the pack with the startpos tables.
campaignstringyesCampaign to check.

Result (StartposValidation)

NameTypeRequiredDescription
issuesarray of StartposIssueyesThe problems, errors first.

startpos.generate

Generates the startpos of a campaign from the tables of an open pack and the game’s files, and adds it to the pack as campaigns/<campaign>/startpos.esf. Unlike startpos.start, it doesn’t launch the game.

The tables are validated first (see startpos.validate), and the startpos isn’t generated if they have errors.

Experimental, and only available for Attila. The record layouts come from the game’s own startpos of the campaign, or of a campaign on the same map for new ones, completed with a layout merged from all the game’s campaigns, and are cached in the config folder.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (GenerateStartpos)

NameTypeRequiredDescription
packstringyesKey of the pack with the startpos tables.
campaignstringyesCampaign to generate the startpos for.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

startpos.extract_tables

Extracts the start_pos_* tables of a campaign from its startpos, the pack’s or the game’s if the pack has none, and adds them to the pack as db/<table>/<campaign>_startpos, with the victory objectives the startpos has as db/victory_objectives.txt (replacing the pack’s). The inverse of startpos.generate.

The startpos doesn’t keep everything: columns the build ignores keep their default value, values the build sums over a province go to one of its regions, row ids other than the factions’ and regions’ are numbered from 1, and loot and raze objectives lose their settlements.

Experimental, and only available for Attila. The record layouts are the ones of startpos.generate.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (ExtractStartposTables)

NameTypeRequiredDescription
packstringyesKey of the pack to add the tables to.
campaignstringyesCampaign whose startpos the tables are extracted from.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

startpos.compare_tables

Extracts the start_pos_* tables of a campaign from its startpos, like startpos.extract_tables, and compares them with the tables of the pack (and the game) the startpos is built from, to find what the build loses or changes. Rows are matched by what they describe, not by their ids.

Experimental, and only available for Attila.

Runs as a job: answers right away with a job ID, and sends its result when the job ends (see Jobs).

Params (CompareStartposTables)

NameTypeRequiredDescription
packstringyesKey of the pack with the startpos tables.
campaignstringyesCampaign whose startpos is compared.

Result (StartposComparison)

NameTypeRequiredDescription
tablesarray of StartposTableComparisonyesOne comparison per extracted table.

startpos.keep_campaign

Removes the rows of the pack’s start_pos_* tables that don’t belong to a campaign, as the Assembly Kit does before building a startpos, so tables holding several campaigns don’t break the build. Tables left without rows are deleted.

Rows of tables with a campaign column belong to their campaign; other rows belong to it if one of their references to a start_pos_* table points to one of its rows (the game’s and the pack’s rows count). Matches the Assembly Kit on Attila’s main campaign.

Params (KeepStartposCampaign)

NameTypeRequiredDescription
packstringyesKey of the pack with the startpos tables.
campaignstringyesCampaign whose rows are kept.

Result (FilesChanged)

NameTypeRequiredDescription
addedarray of stringyesPaths of the added or edited files.
deletedarray of stringyesPaths of the deleted files.

tools.plugin_scripts

Returns the plugin scripts in the scripts folder of the config.

Params (ListPluginScripts)

Empty object: {}.

Result (PluginScripts)

NameTypeRequiredDescription
scriptsarray of stringyesPaths of the scripts.

tools.run_plugin_script

Runs a plugin script over files of an open pack, reading back the files it changes.

Params (RunPluginScript)

NameTypeRequiredDescription
packstringyesKey of the pack.
scriptstringyesPath of the script.
pathsarray of stringyesPaths of the files and folders passed to the script.

Result (PluginScriptRun)

NameTypeRequiredDescription
editedarray of stringyesPaths of the files read back into the pack.
errorstring or nullyesError message of the script, if it failed.

lua.hovers

Returns the docs of the game’s Lua API for the symbols of a Lua script, to show on hover.

Params (GetLuaHovers)

NameTypeRequiredDescription
sourcestringyesSource of the script.

Result (LuaHovers)

NameTypeRequiredDescription
hoversarray of LuaHoveryesThe hovers.

ceo.traits

Returns the trait CEOs of the Assembly Kit tables.

Params (ListTraitCeos)

Empty object: {}.

Result (TraitCeos)

NameTypeRequiredDescription
traitsarray of [string, string]yesUUID and key of each trait CEO.

ceo.add_entries

Adds CEO entries (armour, career, traits and their locs) to an open pack.

Params (AddCeoEntries)

NameTypeRequiredDescription
packstringyesKey of the pack.
entriesarray of CeoEntryDatayesThe entries.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

ceo.build

Builds ceo_data.ccd in the Assembly Kit from the CEO tables of an open pack, running BOB.

Params (BuildCeo)

NameTypeRequiredDescription
packstringyesKey of the pack.
assembly_kitstringyesRoot folder of the Assembly Kit.
bobstringyesPath of BOB’s executable.

Result (Done)

Empty object: {}.

ceo.import

Imports the ceo_data.ccd built by ceo.build into an open pack.

Params (ImportCeo)

NameTypeRequiredDescription
packstringyesKey of the pack.
assembly_kitstringyesRoot folder of the Assembly Kit.

Result (FilesEdited)

NameTypeRequiredDescription
editedarray of stringyesPaths of the edited files.

Types

Type CeoEntryData

A CEO entry to add.

NameTypeRequiredDescription
namestringyesName of the entry, used to build the keys of its rows.
optionstringyesKind of entry: unique for unique ones.
elementstringyesElement of the entry.
genderstringyesGender of the entry.
traitsarray of [string, string]yesUUID and key of each trait of the entry.
expandedbooleanyesIf the entry also starts with a sword and shield.

Type FileRef

A file in one of the sources.

NameTypeRequiredDescription
sourceFileSourceyesWhere the file is.
pathstringyesPath of the file in its source.

Type FileSource

Where a file is.

One of:

  • { "pack": … }: An open pack, by key.
    • pack (string, yes)
  • "game_files": The vanilla files of the selected game.
  • "parent_files": The files of the parent packs of the open packs.
  • "assembly_kit": The tables of the selected game’s Assembly Kit. Their paths are db/<table_name>/ak_data.

Type LuaHover

Docs of a symbol of a Lua script. Lines and columns are 0-based.

NameTypeRequiredDescription
start_lineintegeryesLine where the symbol starts.
start_columnintegeryesColumn where the symbol starts.
end_lineintegeryesLine where the symbol ends.
end_columnintegeryesColumn where the symbol ends.
docsstringyesThe docs, as rich text.

Type MapTile

A tile of a map.

NameTypeRequiredDescription
pathstringyesPath on disk of the tile.
folderstringyesSubfolder of the pack’s terrain tiles folder to add it to.

Type StartposIssue

A problem in the tables a startpos is built from.

NameTypeRequiredDescription
levelDiagnosticLevelyeserror if the build fails or hangs, warning if it works but drops or ignores data.
tablestringyesTable, without the _tables suffix.
rowstringyesKey values of the row, joined by |, or its position (#n) if the table has no keys.
columnstring or nullyesColumn with the problem, if it’s about one.
messagestringyesWhat’s wrong.

Type DiagnosticLevel

Severity of a result.

One of:

  • "info": A suggestion, or information about the mod.
  • "warning": Something that may be a mistake, or cause problems in some situations.
  • "error": Something that will likely cause problems, like invalid references or malformed data.

Type StartposTableComparison

How the rows of one table extracted from a startpos compare with the original ones.

NameTypeRequiredDescription
tablestringyesTable, without the _tables suffix.
match_rationumberyesShare of the original values the startpos keeps, from 0 to 1.
matched_rowsintegeryesRows found in both.
missing_rowsarray of stringyesKeys of the original rows the startpos doesn’t keep.
extra_rowsarray of stringyesKeys of the extracted rows the original tables lack.
columnsarray of StartposColumnComparisonyesThe compared columns with different values.

Type StartposColumnComparison

How the values of one column of the matched rows compare.

NameTypeRequiredDescription
columnstringyesColumn name.
equalintegeryesMatched rows with the same value.
differentintegeryesMatched rows with a different value.
examplesarray of StartposValueDifferenceyesSome of the differences.

Type StartposValueDifference

A value the startpos keeps differently from its table.

NameTypeRequiredDescription
rowstringyesKey of the row.
originalstringyesValue in the original table.
extractedstringyesValue extracted from the startpos.

Translations

Methods about the translations of the open packs.

translations.pack

Returns the whole translation of an open pack to a language, as the translator works with it.

Meant for clients editing whole translations. Others should use translations.list.

Params (GetPackTranslation)

NameTypeRequiredDescription
packstringyesKey of the pack.
source_languagestringyesLanguage code of the source texts, like en.
languagestringyesLanguage code of the translation, like es.

Result (PackTranslation)

One of RPFM’s own types, without a schema. See its docs in rpfm_lib or rpfm_extensions.

translations.submit

Submits the saved translation of a pack to the Translation Hub, opening or updating a pull request.

Params (SubmitTranslation)

NameTypeRequiredDescription
pack_namestringyesName of the pack the translation is for.
source_languagestringyesLanguage code of the source texts, like en.
languagestringyesLanguage code of the translation, like es.

Result (TranslationSubmission)

One of:

  • { "status": "submitted", "created": …, "url": … }: The translation was submitted.
    • created (boolean, yes): If a new pull request was opened. false means an open one was updated.
    • url (string, yes): Web page of the pull request.
  • { "status": "sign_in_required" }: The user has to sign in to GitHub first, because they never did or GitHub rejected their sign-in.

translations.list

Returns the translation of the texts of an open pack to a language.

Texts already translated in the vanilla game, or in previous translations of the pack, come translated.

Params (ListTranslations)

NameTypeRequiredDescription
packstringyesKey of the pack.
source_languagestringno, default "EN"Language the texts of the pack are written in, like EN.
languagestringyesLanguage to translate the texts to, like ES or DE.
filterTranslationFilterno, default "all"Which entries to return.
offsetintegerno, default 0Amount of matching entries to skip.
limitinteger or nullnoMaximum amount of entries to return. Defaults to 200.

Result (Translations)

NameTypeRequiredDescription
entriesarray of TranslationEntryyesThe entries in the page, sorted by key.
totalintegeryesAmount of entries matching the request, in all pages.

translations.generate_vanilla

Generates the vanilla texts of a language from the game’s locale packs, so translations to and from it can reuse them.

Params (GenerateVanillaTexts)

NameTypeRequiredDescription
languagestringyesLanguage of the texts to generate, like EN.

Result (VanillaTextsAvailable)

NameTypeRequiredDescription
availablebooleanyesIf they’re available, generated now or before.

Types

Type TranslationEntry

The translation of a text.

NameTypeRequiredDescription
keystringyesLoc key of the text.
sourcestringyesText in the source language.
translationstringyesText in the target language. Empty if it’s not translated yet.
needs_retranslationbooleanyesIf the source text changed since it was translated.
automaticbooleanyesIf the translation was made automatically, and still needs a review.
removedbooleanyesIf the text no longer exists in the pack.

Type TranslationFilter

Which translation entries to return.

One of:

  • "all": All of them.
  • "untranslated": Entries without a translation.
  • "needs_review": Entries whose source text changed since they were translated, or that were translated automatically.

GitHub

Methods to sign in to GitHub, needed to submit translations to the Translation Hub.

The sign-in uses GitHub’s device flow: the user enters a code on GitHub while the client polls for the result. The token is kept by the server in the OS keyring, and never sent to clients.

github.sign_in_start

Starts a GitHub sign-in, returning the code the user has to enter on GitHub.

Params (StartGitHubSignIn)

Empty object: {}.

Result (DeviceCode)

One of RPFM’s own types, without a schema. See its docs in rpfm_lib or rpfm_extensions.

github.sign_in_poll

Checks if the user approved a sign-in. Once approved, the server keeps the sign-in.

Params (PollGitHubSignIn)

NameTypeRequiredDescription
device_codestringyesDevice code of the sign-in, from github.sign_in_start.

Result (GitHubSignInState)

One of:

  • "Pending": The user hasn’t approved the sign-in yet.
  • { "SlowDown": … }: Polling too fast. Contains the new minimum seconds between polls.
    • SlowDown (integer, yes)
  • { "SignedIn": … }: Signed in. Contains the account’s login.
    • SignedIn (string, yes)
  • "Expired": The code expired before the user approved it.
  • "Denied": The user rejected the sign-in.

github.account

Returns the GitHub account the user is signed in as.

Params (GetGitHubAccount)

Empty object: {}.

Result (GitHubAccount)

NameTypeRequiredDescription
loginstring or nullyesLogin of the account, or None if the user isn’t signed in.

github.sign_out

Signs out of GitHub, deleting the stored sign-in.

Params (SignOutOfGitHub)

Empty object: {}.

Result (Done)

Empty object: {}.

Updates

Methods to check for and download updates of RPFM and of the data it downloads.

These methods don’t touch the session’s state, so they run without waiting for its other requests. Schemas are updated with UpdateSchemas, as they need reloading.

updates.check

Checks if there is an update.

Params (CheckUpdate)

NameTypeRequiredDescription
componentUpdateComponentyesWhat to check.

Result (UpdateStatus)

NameTypeRequiredDescription
availablebooleanyesIf there is an update.
stateUpdateStateyesDetails of the state.
versionstring or nullnoVersion of the update, for program updates.

updates.apply

Downloads an update. Schemas can’t be updated with this, as they need reloading: use schema.update.

Params (ApplyUpdate)

NameTypeRequiredDescription
componentUpdateComponentyesWhat to update.

Result (Done)

Empty object: {}.

Types

Type UpdateComponent

Something that can be updated.

One of:

  • "program": RPFM itself. Updating it replaces its files with the latest release, and needs a restart.
  • "schemas": The schemas with the table definitions of every game.
  • "lua_autogen": The Lua type definitions used for editor support in MyMods.
  • "old_assembly_kit": The Assembly Kit data of Empire and Napoleon, which don’t have one.
  • "translations": The community translations of the Translation Hub.

Type UpdateState

Details of the state of the updates of something.

One of:

  • "new_stable_update": There is a new stable release of the program.
  • "new_beta_update": There is a new beta release of the program.
  • "new_update_hotfix": There is a new hotfix release of the program.
  • "unknown_version": The program’s version couldn’t be compared with the latest release.
  • "new_update": There is a new version of the files.
  • "no_local_files": The files were never downloaded.
  • "diverged": The local files diverged from the remote ones, and need to be downloaded again.
  • "no_update": Everything is up to date.

Jobs

Methods to follow and cancel jobs.

Methods that can take a long time run as jobs (see Request::IS_JOB): they answer right away with a JobStarted, and run in order with the rest of the session’s requests. Their state is sent to clients in JOB_UPDATED_NOTIFICATION notifications, and can be checked or waited for with the methods here, which answer without waiting for other requests.

job.status

Returns the state of a job.

Params (GetJobStatus)

NameTypeRequiredDescription
jobintegeryesID of the job.

Result (JobStatus)

NameTypeRequiredDescription
jobintegeryesID of the job.
methodstringyesMethod the job runs.

Plus one of:

  • { "state": "queued" }: Waiting for the requests before it to finish.
  • { "state": "running", "progress"?: …, "stage"?: … }: Running.
    • progress (integer or null, no): How far along the job is, from 0 to 100, if it reports it.
    • stage (string or null, no): Step the job is on, if it reports them.
  • { "state": "finished", "result": … }: Finished, with the response of its method.
    • result (any, yes): Response of the job’s method.
  • { "state": "failed", "error": … }: Failed, with the error of its method.
    • error (RpcError, yes): Why the job failed.
  • { "state": "cancelled" }: Cancelled before it started.

job.wait

Waits for a job to end, and returns its state.

Params (WaitForJob)

NameTypeRequiredDescription
jobintegeryesID of the job.
timeout_secsinteger or nullnoMaximum amount of seconds to wait. If the job hasn’t ended by then, its current state is returned. Defaults to 60.

Result (JobStatus)

NameTypeRequiredDescription
jobintegeryesID of the job.
methodstringyesMethod the job runs.

Plus one of:

  • { "state": "queued" }: Waiting for the requests before it to finish.
  • { "state": "running", "progress"?: …, "stage"?: … }: Running.
    • progress (integer or null, no): How far along the job is, from 0 to 100, if it reports it.
    • stage (string or null, no): Step the job is on, if it reports them.
  • { "state": "finished", "result": … }: Finished, with the response of its method.
    • result (any, yes): Response of the job’s method.
  • { "state": "failed", "error": … }: Failed, with the error of its method.
    • error (RpcError, yes): Why the job failed.
  • { "state": "cancelled" }: Cancelled before it started.

job.cancel

Cancels a job that hasn’t started yet. Running jobs can’t be cancelled.

Params (CancelJob)

NameTypeRequiredDescription
jobintegeryesID of the job.

Result (Done)

Empty object: {}.

Types

Type RpcError

A JSON-RPC error.

NameTypeRequiredDescription
codeintegeryesNumeric code of the error. See ApiError::code.
messagestringyesHuman-readable description of the error.
dataanynoThe ApiError this was built from, serialized, so clients can match on it.

MCP Interface

In addition to the WebSocket protocol, the RPFM Server exposes a Model Context Protocol (MCP) interface at /mcp. This allows AI assistants (such as Claude, Cursor, or any MCP-compatible client) to interact with RPFM using the standard MCP specification.

Transport

The MCP endpoint uses Streamable HTTP transport:

POST http://127.0.0.1:45127/mcp

All MCP clients share one RPFM session, separate from the WebSocket ones, whatever MCP version they use: two MCP clients see the same selected game and open packs. The session is kept as long as it has open packs or unfinished jobs. Otherwise, the server removes it after 5 minutes without tool calls, and the next tool call starts a new one, with nothing loaded.

The MCP session starts with the settings in RPFM’s settings.json, and can’t change them.

How It Differs from WebSocket

AspectWebSocket (/ws)MCP (/mcp)
ProtocolJSON-RPC 2.0MCP (JSON-RPC 2.0)
TransportWebSocketStreamable HTTP
Interaction modelCall methodsCall named tools
Slow operationsReturn a job ID, followed by job.updated notificationsWait up to 45 seconds for the job, then return its state
Session control?session_id= and session.disconnectOne shared session, removed after 5 minutes of inactivity if it has no open packs or unfinished jobs
Intended clientsCustom scripts, GUIsAI assistants and MCP-compatible tools

Every tool calls one method, with the same params and result, so the Methods reference also documents the tools. The tools only cover what’s useful to an assistant: methods made for the UI, like the ones returning whole decoded files, have no tool.

Connecting

Claude Desktop

Add the server to your claude_desktop_config.json:

{
  "mcpServers": {
    "rpfm": {
      "url": "http://127.0.0.1:45127/mcp"
    }
  }
}

Claude Code

claude mcp add --transport http rpfm http://127.0.0.1:45127/mcp

Other MCP Clients

Any MCP client that supports Streamable HTTP transport can connect. Point it to http://127.0.0.1:45127/mcp.

Results and Errors

Tools return their result as structured content, and their params and results are described by JSON schemas in tools/list.

A tool that fails returns a tool error instead of ending the MCP session. Its content has the same code, message and data.kind as a WebSocket error, like pack_not_found or schema_not_loaded.

Jobs

Slow tools (set_game, generate_dependencies_cache, rebuild_dependencies, run_diagnostics, run_search, update_schemas, update_schema_from_assembly_kit, optimize_pack, run_lua_tests) run as jobs. They wait up to 45 seconds and return the state of their job:

  • If it finished, the state includes the result of the tool.
  • If it failed, the tool returns its error, like any other tool that fails.
  • If it’s still running, call wait_for_job with its job ID, as many times as needed. It ends like the tool that started the job.

Other tools called meanwhile wait for the job to end before running, except during run_diagnostics: a running check stops for them, and starts again after them.

Resources

The server also exposes reference data as MCP resources, readable without tool calls:

URIContent
rpfm://gamesKeys of the supported games.
rpfm://enums/PFHFileTypeValid pack types.
rpfm://enums/CompressionFormatValid compression formats.
rpfm://enums/SupportedFormatsValid video formats.
rpfm://reference/initializationHow to set up a session.
rpfm://reference/path_conventionsCommon file paths inside packs.

Typical Workflow

  1. Set the game: set_game with the game key. This loads the schema and the dependencies.
  2. Open a pack: open_pack with the path of the pack, or new_pack. Both return the pack’s key.
  3. Browse files: list_files with the pack as source.
  4. Read data: table_info and table_rows for DB and Loc tables, read_file for anything else.
  5. Modify data: edit_table for tables, write_file for anything else.
  6. Save: save_pack.

Example: Editing a DB Table

Call set_game:

{ "game": "warhammer_3" }

Call open_pack:

{ "paths": ["/path/to/my_mod.pack"] }

The result contains the pack’s key, a UUID like 0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21, used by the next calls as pack. It never changes while the pack is open, even if it’s saved under another name. session_status lists the keys of all open packs, with their names and paths.

Call table_rows, to get the rows of a unit:

{
  "file": { "source": { "pack": "0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21" }, "path": "db/land_units_tables/my_mod" },
  "columns": ["key", "num_men"],
  "filters": [{ "column": "key", "op": "equals", "value": "my_unit" }]
}

Each returned row has the index of the row in the table. Call edit_table to change it:

{
  "pack": "0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21",
  "path": "db/land_units_tables/my_mod",
  "edits": [{ "op": "update", "index": 4, "values": { "num_men": 120 } }]
}

Call save_pack:

{ "pack": "0b4c5a62-7d3e-4f1a-9c2b-8e6d5f4a3b21" }

To change a vanilla table, copy it into your pack first with copy_files, from the "game_files" source.

Tool Reference

The MCP interface exposes 95 tools. The descriptions below are the first sentence of each tool’s description; tools/list has the full descriptions and the JSON schemas of their params.

Session and jobs

ToolMethodDescription
session_statussession.statusGet the state of the session: the selected game, if its schema and dependencies (vanilla files, Assembly Kit tables, parent packs) are loaded, and the open packs with their keys.
set_gamesession.set_gameSelect the game to work with, like warhammer_3, loading its schema and, by default, its dependencies (vanilla files, Assembly Kit tables, parent packs).
job_statusjob.statusGet the state of a job: queued, running (with its current step), finished (with its result), failed (with its error) or cancelled.
wait_for_jobjob.waitWait for a job to end, up to timeout_secs (60 by default), and return its state, or its error if it failed.
cancel_jobjob.cancelCancel a job that hasn’t started yet.

Packs

ToolMethodDescription
pack_infopack.infoGet the details of an open pack: type, format version, compression, encryption flags, the packs it depends on, and its MyMod mode.
pack_settingspack.settingsGet the settings of an open pack, like its diagnostics ignore rules (diagnostics_files_to_ignore), files to ignore when importing, or if it has autosaves disabled.
update_pack_settingspack.update_settingsChange settings of an open pack.
new_packpack.newCreate a new empty pack.
open_packpack.openOpen one or more packs from disk, merged into a single one.
open_vanilla_packspack.open_vanillaOpen all the vanilla packs of the selected game, merged into a single one, to browse them like any open pack.
close_packpack.closeClose an open pack.
close_all_packspack.close_allClose all open packs.
save_packpack.saveSave an open pack to disk, to its current path or to a new path.
update_packpack.updateChange properties of an open pack: type, compression, encryption and timestamp flags, the packs it depends on, and its MyMod mode.

Files

ToolMethodDescription
list_filesfiles.listList the files of an open pack, the game files, the parent packs, or the Assembly Kit tables, sorted by path.
read_filefile.readRead a file of an open pack, the game files or the parent packs: as text for scripts, XML, JSON and other text files; as decoded JSON for structured formats (portrait settings, unit variants, models, etc.); or as raw base64 bytes, best for images and other binary files.
write_filefile.writeReplace the contents of a file of an open pack: text for text files, decoded JSON in the format read_file returns, or raw base64 bytes (which also creates the file if it doesn’t exist).
list_animpackanimpack.listList the files inside an AnimPack of an open pack, the game files or the parent packs.
add_to_animpackanimpack.addCopy files of an open pack into an AnimPack of an open pack.
extract_from_animpackanimpack.extractCopy files of an AnimPack of any source into an open pack.
delete_from_animpackanimpack.deleteDelete files from an AnimPack of an open pack.
create_filefiles.createCreate a new empty file in an open pack: a DB table (with the version of the game files by default), a Loc table, a text file or an AnimPack.
add_files_from_diskfiles.add_from_diskAdd files and folders from disk to an open pack, under a folder of the pack.
copy_filesfiles.copyCopy files and folders from an open pack, the game files, the parent packs or the Assembly Kit tables into an open pack, keeping their paths.
delete_filesfiles.deleteDelete files and folders from an open pack.
rename_filesfiles.renameRename or move files and folders of an open pack.
duplicate_filesfiles.duplicateCopy files of an open pack in the same pack, adding a number to their names.
extract_filesfiles.extractExtract files and folders of an open pack, the game files or the parent packs to a folder on disk, optionally with tables as TSV.

Tables

ToolMethodDescription
table_infotable.infoGet the columns (name, type, key, referenced table without its _tables suffix and column, lookups, default value, file paths, description, if unused) and the row count of a DB or Loc table, from any source.
table_rowstable.rowsRead rows of a DB or Loc table, from any source, as plain values (booleans, numbers and strings).
column_valuestable.column_valuesGet the distinct values of a column of a table, like all the faction keys of factions_tables, from the open packs and the dependencies, sorted.
unused_numberstable.unused_numbersGet numbers no row uses yet in an integer column of a table, like unique_index of main_units_tables, for new rows that need unique ones.
merge_tablestable.mergeMerge tables of the same type of an open pack into a new one.
upgrade_tabletable.upgradeUpdate a table of an open pack to the version it has in the game files, after a game update.
rename_keytable.rename_keyChange a key value of a table in every table of an open pack: the key itself, the columns referencing it, and the loc keys generated from it.
add_key_deletestable.add_key_deletesAdd keys to a key deletes table (db/twad_key_deletes_tables/<file_name>) of an open pack, to delete those keys of a table in the game.
export_tsvtable.export_tsvWrite a table of an open pack, the game files or the parent packs to a TSV file, to edit it in a spreadsheet.
import_tsvtable.import_tsvReplace a table of an open pack with the contents of a TSV file, keeping its GUID.
edit_tabletable.editEdit rows of a DB or Loc table in an open pack: insert, update and delete rows by index, with values given by column name (see table_info for the columns).

Schema

ToolMethodDescription
table_definitionschema.definitionGet the columns of a table as defined in the schema, without needing a file of it.
schema_tablesschema.tablesList the tables of the selected game’s schema whose name starts with a prefix, with the versions it has definitions for, newest first.
patch_columnschema.patch_columnChange how the schema describes a column with a local patch: description, if it’s a key, default value, referenced table and column (is_reference as table;column), if it holds file paths, if it can’t be empty, or if it’s unused.
remove_patchesschema.remove_patchesRemove the local schema patches of a table, or of one of its columns.
update_schemasschema.updateDownload the latest schemas, reload the selected game’s one, and rebuild the dependencies.
update_schema_from_assembly_kitschema.update_from_assembly_kitUpdate the selected game’s schema with the table definitions of its Assembly Kit, and save it.
raw_definitionsschema.raw_definitionsGet the definitions of a table as the schema stores them, to edit them with set_definition.
set_definitionschema.set_definitionAdd a definition to the selected game’s schema, or replace the one with its version, then save and reload the schema.
delete_definitionschema.delete_definitionRemove a definition from the selected game’s schema, then save and reload the schema.
referencing_columnsschema.referencing_columnsGet the columns of other tables referencing each column of a table, according to the schema.
table_patchesschema.patchesGet the patches applied to the columns of a table definition: the local ones made with patch_column, and the ones included in the schema.
missing_definitionsschema.missing_definitionsList the tables of an open pack with rows the schema can’t decode, which need a new definition.
import_patchesschema.import_patchesAdd patches to the selected game’s schema itself, and save it.

Dependencies

ToolMethodDescription
generate_dependencies_cachedependencies.generate_cacheGenerate the dependencies cache of the selected game from its files and Assembly Kit, and load it.
rebuild_dependenciesdependencies.rebuildReload the dependencies of the selected game, like after changing the packs the open packs depend on.
dependency_tablesdependencies.tablesList the tables of the selected game’s files, and the startpos and twad tables of its schema, with the version new tables of each type should use.

References

ToolMethodDescription
find_definitionreferences.definitionFind the row where a value of a referenced table is defined, like the row of factions_tables with a faction key.
find_usagesreferences.usagesFind the rows of other tables referencing a value of a table, like everything using a faction key.
find_locreferences.locFind the row of a Loc file with a key.
row_locsreferences.row_locsGet the loc entries of a row of a DB table, from any source: the loc key of each of its localised columns, like land_units_onscreen_name_my_unit for onscreen_name, with its text, or null if no Loc file has it.
loc_sourcereferences.loc_sourceGet the table, localised column and key values a loc key belongs to, like factions_tables, screen_name and ["wh_main_emp_empire"] for factions_screen_name_wh_main_emp_empire.
reference_valuesreferences.valuesGet the values a reference column of a table can have, with their display text (like the name of each referenced row), from the open packs and the dependencies.
ToolMethodDescription
run_diagnosticsdiagnostics.runCheck the open packs for problems (invalid references, duplicated keys, outdated tables, script errors, etc.), and return a summary by level and type.
list_diagnosticsdiagnostics.listList results of the last run_diagnostics, filtered by level, report type, pack and path prefix, in pages (100 by default; the total of matching results is always returned).
ignore_diagnosticsdiagnostics.ignoreMake the next diagnostics checks of a pack skip results of files under a path, optionally only for some columns and report types.
run_searchsearch.runSearch text (or a regex) in open packs, the game files, the parent packs, the Assembly Kit tables, or the schema’s column names, and return a summary of the matches by file type.
list_search_matchessearch.matchesList matches of the last run_search, filtered by file type and path prefix, in pages (100 by default; the total of matching matches is always returned).
replace_search_matchessearch.replaceReplace matches of the last run_search by ID, or all of them, with a text.
run_lua_testslua.run_testsRun Lua tests against the scripts of all open packs, outside of the game.

Notes

ToolMethodDescription
list_notesnotes.listGet the notes (comments) attached to a file or folder of an open pack, or all of them.
add_notenotes.addAttach a note (comment, with an optional link) to a file or folder of an open pack, or replace one by passing its id.
delete_notenotes.deleteDelete a note of an open pack.

Translations

ToolMethodDescription
list_translationstranslations.listGet the translation of the texts of an open pack to a language, reusing vanilla and previous translations.
generate_vanilla_textstranslations.generate_vanillaGenerate the vanilla texts of a language from the game’s locale packs, so translations to and from it can reuse them.

Tools

ToolMethodDescription
optimizer_optionstools.optimizer_optionsGet the optimizer options as the server’s settings have them, by name.
patch_siege_aitools.patch_siege_aiPatch the siege maps of an open pack so the AI can use them.
pack_maptools.pack_mapAdd the tiles and tile maps of a map exported by Terry to an open pack.
generate_missing_locstools.generate_missing_locsAdd empty loc entries for the localised columns of the tables of the open packs that don’t have them yet.
update_anim_idstools.update_anim_idsOffset the animation ids of an open pack from a starting id, like after a game update moves them.
anims_by_skeletontools.anims_by_skeletonGet the paths of the animations using a skeleton, in the open packs and the dependencies.
export_gltftools.export_gltfExport a RigidModel of any source to a glTF file, with its textures.
set_video_formattools.set_video_formatChange the format of a ca_vp8 video of an open pack: CaVp8 or Ivf.
live_exporttools.live_exportExport the scripts and UI files of an open pack to the game’s data folder, to test them without saving the pack.
init_mymodtools.init_mymodCreate the folder of a new MyMod in the MyMods folder of the settings, with optional editor configs for Lua scripting and a git repository.
startpos_campaignsstartpos.campaignsGet the campaigns a startpos can be built for, and the one the pack’s last startpos was built for.
start_startposstartpos.startStart building a startpos for a campaign with the tables of an open pack: prepares the Assembly Kit and launches the game.
finish_startposstartpos.finishFinish building a startpos after the game was closed: imports it into the pack, or cancels the build with cancel: true.
validate_startposstartpos.validateCheck the tables a campaign’s startpos is built from: broken references and data that make the game’s build fail, hang or drop rows.
generate_startposstartpos.generateGenerate the startpos of a campaign from the tables of an open pack and the game’s files, without launching the game, and add it to the pack.
extract_startpos_tablesstartpos.extract_tablesExtract the start_pos tables and victory objectives of a campaign from its startpos (the pack’s, or the game’s) and add them to the pack.
keep_startpos_campaignstartpos.keep_campaignRemove the rows of an open pack’s start_pos tables that don’t belong to a campaign, as the Assembly Kit does before building a startpos, so tables holding several campaigns don’t break the build.
compare_startpos_tablesstartpos.compare_tablesCompare the tables extracted from a campaign’s startpos with the tables it’s built from, to find what the build loses or changes.
optimize_packtools.optimizeRemove data of an open pack that’s identical to vanilla or unneeded (duplicated rows, unchanged rows and files, empty tables, etc.).

Updates

ToolMethodDescription
check_updateupdates.checkCheck if there is an update of RPFM (program), the schemas, the Lua type definitions (lua_autogen), the Empire and Napoleon Assembly Kit data (old_assembly_kit), or the community translations.
apply_updateupdates.applyDownload the update of the Lua type definitions (lua_autogen), the Empire and Napoleon Assembly Kit data (old_assembly_kit), the community translations, or RPFM itself (program, which replaces its files and needs a restart: only do it if the user asks).

Client Example

This page provides a complete client implementation example. The same patterns apply to any language with WebSocket and JSON support. See the WebSocket protocol for the message format, and Methods for the params and results of each method.

The Client

The client below wraps the WebSocket connection, matches responses to their requests by id, waits for jobs, and provides typed methods for common operations.

Connection and Requests

interface RpcError {
  code: number;
  message: string;
  data?: { kind: string; details?: string };
}

type JobStatus =
  { job: number; method: string } & (
    | { state: "queued" }
    | { state: "running"; stage?: string }
    | { state: "finished"; result: any }
    | { state: "failed"; error: RpcError }
    | { state: "cancelled" }
  );

class RpfmError extends Error {
  constructor(public error: RpcError) {
    super(error.message);
  }

  get kind(): string | undefined {
    return this.error.data?.kind;
  }
}

class RpfmClient {
  private ws: WebSocket;
  private nextId = 1;
  private pending = new Map<number, { resolve: (result: any) => void; reject: (error: Error) => void }>();
  public sessionId: number | null = null;

  /** Called with every `job.updated` notification, to show progress. */
  public onJobUpdated: (status: JobStatus) => void = () => {};

  private constructor(ws: WebSocket) {
    this.ws = ws;
    this.ws.onmessage = (event) => this.handleMessage(JSON.parse(event.data));
    this.ws.onclose = () => {
      for (const { reject } of this.pending.values()) {
        reject(new Error("Connection closed"));
      }
      this.pending.clear();
    };
  }

  /** Connects to the server, optionally reattaching to an existing session. */
  static connect(url = "ws://127.0.0.1:45127/ws", sessionId?: number): Promise<RpfmClient> {
    const fullUrl = sessionId !== undefined ? `${url}?session_id=${sessionId}` : url;
    const client = new RpfmClient(new WebSocket(fullUrl));

    // The connection is ready once the server tells us our session ID.
    return new Promise((resolve, reject) => {
      client.ws.addEventListener("message", function onConnected(event) {
        const message = JSON.parse(event.data);
        if (message.method === "session.connected") {
          client.ws.removeEventListener("message", onConnected);
          resolve(client);
        }
      });
      client.ws.addEventListener("error", () => reject(new Error("Connection failed")));
    });
  }

  private handleMessage(message: any) {
    if ("id" in message) {
      const pending = this.pending.get(message.id);
      this.pending.delete(message.id);
      if (message.error) {
        pending?.reject(new RpfmError(message.error));
      } else {
        pending?.resolve(message.result);
      }
    } else if (message.method === "session.connected") {
      this.sessionId = message.params.session_id;
    } else if (message.method === "job.updated") {
      this.onJobUpdated(message.params);
    }
  }

  /** Calls a method and returns its result. Fails with an `RpfmError` if the method fails. */
  call<T = any>(method: string, params?: object): Promise<T> {
    const id = this.nextId++;
    return new Promise((resolve, reject) => {
      this.pending.set(id, { resolve, reject });
      this.ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
    });
  }

  /** Calls a method that runs as a job, and returns its result once the job ends. */
  async runJob<T = any>(method: string, params?: object): Promise<T> {
    const { job } = await this.call<{ job: number }>(method, params);
    while (true) {
      const status = await this.call<JobStatus>("job.wait", { job, timeout_secs: 60 });
      switch (status.state) {
        case "finished": return status.result;
        case "failed": throw new RpfmError(status.error);
        case "cancelled": throw new Error(`Job ${job} was cancelled`);
        case "queued":
        case "running": break;
      }
    }
  }

  /** Ends the session and closes the connection. */
  async disconnect(): Promise<void> {
    await this.call("session.disconnect");
    this.ws.close();
  }
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;

public class RpfmException : Exception
{
    public int Code { get; }
    public string? Kind { get; }

    public RpfmException(JsonNode error) : base(error["message"]?.GetValue<string>())
    {
        Code = error["code"]!.GetValue<int>();
        Kind = error["data"]?["kind"]?.GetValue<string>();
    }
}

public class RpfmClient : IAsyncDisposable
{
    private readonly ClientWebSocket _ws = new();
    private readonly ConcurrentDictionary<long, TaskCompletionSource<JsonNode?>> _pending = new();
    private readonly TaskCompletionSource _connected = new();
    private long _nextId = 1;

    public long? SessionId { get; private set; }

    /// Called with every `job.updated` notification, to show progress.
    public event Action<JsonNode>? JobUpdated;

    /// Connects to the server, optionally reattaching to an existing session.
    public static async Task<RpfmClient> ConnectAsync(string url = "ws://127.0.0.1:45127/ws", long? sessionId = null)
    {
        var client = new RpfmClient();
        var fullUrl = sessionId is null ? url : $"{url}?session_id={sessionId}";
        await client._ws.ConnectAsync(new Uri(fullUrl), CancellationToken.None);
        _ = client.ReceiveLoopAsync();

        // The connection is ready once the server tells us our session ID.
        await client._connected.Task;
        return client;
    }

    private async Task ReceiveLoopAsync()
    {
        var buffer = new byte[64 * 1024];
        var message = new MemoryStream();
        while (_ws.State == WebSocketState.Open)
        {
            var result = await _ws.ReceiveAsync(buffer, CancellationToken.None);
            if (result.MessageType == WebSocketMessageType.Close) break;

            message.Write(buffer, 0, result.Count);
            if (!result.EndOfMessage) continue;

            HandleMessage(JsonNode.Parse(message.ToArray())!);
            message.SetLength(0);
        }

        foreach (var pending in _pending.Values)
            pending.TrySetException(new Exception("Connection closed"));
    }

    private void HandleMessage(JsonNode message)
    {
        if (message["id"] is JsonNode id)
        {
            if (!_pending.TryRemove(id.GetValue<long>(), out var pending)) return;
            if (message["error"] is JsonNode error)
                pending.SetException(new RpfmException(error));
            else
                pending.SetResult(message["result"]);
        }
        else if (message["method"]?.GetValue<string>() == "session.connected")
        {
            SessionId = message["params"]!["session_id"]!.GetValue<long>();
            _connected.TrySetResult();
        }
        else if (message["method"]?.GetValue<string>() == "job.updated")
        {
            JobUpdated?.Invoke(message["params"]!);
        }
    }

    /// Calls a method and returns its result. Throws an `RpfmException` if the method fails.
    public async Task<JsonNode?> CallAsync(string method, object? @params = null)
    {
        var id = Interlocked.Increment(ref _nextId);
        var pending = new TaskCompletionSource<JsonNode?>(TaskCreationOptions.RunContinuationsAsynchronously);
        _pending[id] = pending;

        var request = JsonSerializer.SerializeToUtf8Bytes(new { jsonrpc = "2.0", id, method, @params });
        await _ws.SendAsync(request, WebSocketMessageType.Text, true, CancellationToken.None);
        return await pending.Task;
    }

    /// Calls a method that runs as a job, and returns its result once the job ends.
    public async Task<JsonNode?> RunJobAsync(string method, object? @params = null)
    {
        var job = (await CallAsync(method, @params))!["job"]!.GetValue<long>();
        while (true)
        {
            var status = (await CallAsync("job.wait", new { job, timeout_secs = 60 }))!;
            switch (status["state"]!.GetValue<string>())
            {
                case "finished": return status["result"];
                case "failed": throw new RpfmException(status["error"]!);
                case "cancelled": throw new Exception($"Job {job} was cancelled");
            }
        }
    }

    /// Ends the session and closes the connection.
    public async ValueTask DisposeAsync()
    {
        await CallAsync("session.disconnect");
        await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
    }

Typed Methods

Wrapping the methods you use gives you typed params and results. A few common ones:

  setGame(game: string): Promise<SessionStatus> {
    return this.runJob("session.set_game", { game });
  }

  openPack(paths: string[]): Promise<PackSummary> {
    return this.call("pack.open", { paths });
  }

  listFiles(pack: string, pathPrefix = "", limit?: number): Promise<FileList> {
    return this.call("files.list", { source: { pack }, path_prefix: pathPrefix, limit });
  }

  tableInfo(file: FileRef): Promise<TableInfo> {
    return this.call("table.info", { file });
  }

  tableRows(file: FileRef, options: { columns?: string[]; filters?: RowFilter[]; offset?: number; limit?: number } = {}): Promise<TableRows> {
    return this.call("table.rows", { file, ...options });
  }

  editTable(pack: string, path: string, edits: RowEdit[]): Promise<{ row_count: number }> {
    return this.call("table.edit", { pack, path, edits });
  }

  readText(file: FileRef): Promise<string> {
    return this.call("file.read", { file, format: "text" }).then((file) => file.contents.text);
  }

  writeText(pack: string, path: string, text: string): Promise<void> {
    return this.call("file.write", { pack, path, contents: { kind: "text", text } });
  }

  savePack(pack: string, path?: string): Promise<PackSummary> {
    return this.call("pack.save", { pack, path });
  }
}

type FileSource = { pack: string } | "game_files" | "parent_files" | "assembly_kit";

interface FileRef {
  source: FileSource;
  path: string;
}

interface PackSummary {
  key: string;
  name: string;
  path: string;
  pack_type: string;
  file_count: number;
}

interface SessionStatus {
  game: string;
  schema_loaded: boolean;
  packs: PackSummary[];
}

interface FileList {
  files: { path: string; file_type: string }[];
  folders: string[];
  total: number;
}

interface TableInfo {
  table_name: string;
  version: number;
  columns: { name: string }[];
  row_count: number;
}

interface RowFilter {
  column: string;
  op: "equals" | "not_equals" | "contains";
  value: string;
  ignore_case?: boolean;
}

interface TableRows {
  columns: string[];
  rows: { index: number; values: (boolean | number | string)[] }[];
  total: number;
}

type RowEdit =
  | { op: "insert"; index?: number; values?: Record<string, boolean | number | string> }
  | { op: "update"; index: number; values: Record<string, boolean | number | string> }
  | { op: "delete"; indexes: number[] };
    public async Task<string> OpenPackAsync(string path) =>
        (await CallAsync("pack.open", new { paths = new[] { path } }))!["key"]!.GetValue<string>();

    public Task<JsonNode?> SetGameAsync(string game) =>
        RunJobAsync("session.set_game", new { game });

    public Task<JsonNode?> TableRowsAsync(object file, object[]? filters = null, int? limit = null) =>
        CallAsync("table.rows", new { file, filters = filters ?? [], limit });

    public Task<JsonNode?> EditTableAsync(string pack, string path, params object[] edits) =>
        CallAsync("table.edit", new { pack, path, edits });

    public Task<JsonNode?> SavePackAsync(string pack) =>
        CallAsync("pack.save", new { pack });

    public static object PackFile(string pack, string path) =>
        new { source = new { pack }, path };
}

Usage Example

const client = await RpfmClient.connect();
client.onJobUpdated = (status) => {
  if (status.state === "running" && status.stage) {
    console.log(`${status.method}: ${status.stage}`);
  }
};

await client.setGame("warhammer_3");
const pack = await client.openPack(["/path/to/my_mod.pack"]);

const tables = await client.listFiles(pack.key, "db/");
console.log(`${tables.total} tables`, tables.files.map((file) => file.path));

// Double the men of a unit.
const units: FileRef = { source: { pack: pack.key }, path: "db/land_units_tables/my_mod" };
const page = await client.tableRows(units, {
  columns: ["key", "num_men"],
  filters: [{ column: "key", op: "equals", value: "my_unit" }],
});

for (const row of page.rows) {
  const numMen = row.values[1] as number;
  await client.editTable(pack.key, units.path, [{ op: "update", index: row.index, values: { num_men: numMen * 2 } }]);
}

try {
  await client.tableRows({ source: { pack: pack.key }, path: "db/missing_tables/data" });
} catch (error) {
  if (error instanceof RpfmError && error.kind === "file_not_found") {
    console.log("No such table");
  }
}

await client.savePack(pack.key);
await client.disconnect();
await using var client = await RpfmClient.ConnectAsync();
client.JobUpdated += status => Console.WriteLine($"{status["method"]}: {status["state"]}");

await client.SetGameAsync("warhammer_3");
var packKey = await client.OpenPackAsync("/path/to/my_mod.pack");

// Double the men of a unit.
var units = RpfmClient.PackFile(packKey, "db/land_units_tables/my_mod");
var page = await client.TableRowsAsync(units, [new { column = "key", op = "equals", value = "my_unit" }]);

foreach (var row in page!["rows"]!.AsArray())
{
    var columns = page["columns"]!.AsArray().Select(column => column!.GetValue<string>()).ToList();
    var numMen = row!["values"]![columns.IndexOf("num_men")]!.GetValue<int>();
    await client.EditTableAsync(packKey, "db/land_units_tables/my_mod", new { op = "update", index = row["index"]!.GetValue<int>(), values = new { num_men = numMen * 2 } });
}

try
{
    await client.TableRowsAsync(RpfmClient.PackFile(packKey, "db/missing_tables/data"));
}
catch (RpfmException error) when (error.Kind == "file_not_found")
{
    Console.WriteLine("No such table");
}

await client.SavePackAsync(packKey);

Adapting to Other Languages

The protocol is language-agnostic. To implement a client in another language:

  1. Connect to ws://127.0.0.1:45127/ws using any WebSocket library, and wait for the session.connected notification.
  2. Send JSON-RPC requests: { "jsonrpc": "2.0", "id": <number>, "method": "<method>", "params": { ... } }.
  3. Receive messages: the ones with an id are responses, matched to their request by it; the ones with a method are notifications.
  4. Handle errors by their data.kind, not their message.
  5. Wait for jobs: methods running as jobs return { "job": <id> }. Call job.wait until the job’s state is finished, failed or cancelled, or follow its job.updated notifications.
  6. Call session.disconnect before closing the connection.

The Rust types of every method, in the rpfm_ipc::api module, are the reference for the JSON of params and results. Their docs are in cargo doc -p rpfm_ipc --open.

Settings & preferences

Open the Preferences dialog from PackFile → Settings (or via its shortcut, Ctrl+,).

Settings are stored on the server side and cached client-side, so they’re consistent across rpfm_ui runs and across multiple UIs talking to the same rpfm_server. Most changes take effect immediately; a few (notably language and other UI-layout settings) require a restart.

The dialog is a single scrollable form with a left-hand navigation pane that jumps to one of eight sections. The list below is a tour, not an exhaustive reference — for the full list of setting keys see the rpfm_ipc::settings_keys API docs.

Paths

The first section you’ll touch as a new user. Holds two groups:

  • Game paths — for each supported game, a collapsible “spoiler” with the game install path and (where applicable) the Assembly Kit install path.
  • Extra paths — MyMod base path (root folder for MyMod projects), Secondary path (extra mods folder in addition to the game’s data/), and Custom config folder (see below).

Custom config folder

By default RPFM keeps its config, schemas, autosaves and crash reports in the OS config folder (see Where settings live on disk). The Custom config folder field lets you point RPFM at a different folder instead — handy for portable installs or keeping everything on a separate drive. Leave it empty to use the default OS folder.

A few caveats: this path is stored separately from the rest of the settings (so it survives a settings reset), changing it recreates the whole config tree at the new location, and existing data is not moved automatically — copy it over yourself if you want to keep your current schemas and caches.

General

  • Language — RPFM’s UI language. Requires a restart.
  • Default game — which Total War game RPFM starts on.
  • Update channel — Stable or Beta.
  • Autosave amount and Autosave interval (in minutes).
  • Check for X on start toggles for program, schema, lua-autogen and old-AK updates.
  • Allow editing of CA Packfiles, Disable file previews, Start maximized.
  • Auto-select Open Packs in Global Search — newly opened Packs are ticked as Global Search sources. Uncheck it to pick them manually.
  • Pack-tree behaviour toggles: expand on add, include base folder on add-from-folder, delete empty folders on delete, ignore game files in AK, multifolder file picker, drag-and-drop in the pack contents tree.

Table

UI behaviour for the table editors:

  • Adjust columns to content, Disable combos on tables, Extend last column, Tight table mode, Resize on edit.
  • Tables use old column order (and a TSV variant) — restores the legacy column ordering.
  • Disable UUID regeneration on DB tables — don’t auto-generate a new UUID on table save.
  • Use right-side markers, Enable lookups, Enable icons, Enable diff markers.
  • New filter chips join the previous group by default — new filter chips are AND-combined with the last one instead of getting their own (OR-combined) group. Can be overridden per chip with its @N suffix or its gear menu.
  • Colour pickers for Added, Modified, Error, Warning and Info marks (separate light/dark colour for each).

Debug

Hidden behind a Debug section header rather than a feature flag, but most of these only matter to developers:

  • Check for missing table definitions, Enable debug menu (exposes the hidden Debug menu in the menu bar), Enable unit editor, Enable ESF editor, Use debug view for Unit Variant, Enable renderer (only when built with support_model_renderer).
  • Use lazy loading — defer decoding files until they’re opened.
  • Action buttons: Clear Dependencies Cache Folder, Clear Autosave Folder, Clear Schema Folder, Clear Layout Settings, Add RPFM to Runcher Tools.

Diagnostics

  • Trigger diagnostics on Pack open.
  • Trigger diagnostics on table edit.

There are no per-check toggles in the Preferences dialog — per-diagnostic ignores are configured per Pack via the Pack Settings tab and via the Diagnostics panel’s right-click “Ignore…” actions.

Telemetry

  • Enable Usage Telemetry — opt-out anonymous action counters.
  • Enable Crash Reports — opt-out automatic crash report upload to Sentry.

Both default to on. Both take effect immediately. See Telemetry & crash reports for the full picture.

AI

API keys for the AI-backed features (used by the Translator, among others):

  • OpenAI API key.
  • DeepL API key.

GitHub

  • GitHub account — the account used to submit translations to the Translation Hub from the Translator. Sign in opens GitHub in your browser and copies a code to your clipboard; enter it there to approve RPFM. The sign-in is kept in your system’s keyring, not in the settings file. Sign out removes it.

Shortcuts

The Preferences dialog has a Shortcuts button at the bottom that opens a separate KDE-style shortcuts dialog where every action can be rebound. See Keyboard shortcuts.

Where settings live on disk

  • Server-side authoritative copy: <config>/settings.json. The exact <config> folder depends on platform (e.g. ~/.config/rpfm/ on Linux, %AppData%\rpfm\ on Windows). On debug builds, it’s the working directory.
  • Backup: <config>/settings.json.bak holds the last copy that loaded successfully. If settings.json is missing, empty or broken, RPFM restores it from the backup instead of falling back to the defaults. Settings are written to a temporary file first and then moved over the real one, so a crash mid-save can’t truncate them.
  • UI-side cache: in-memory only — refreshed from the server on launch and on each preferences-saved event.

Keyboard shortcuts

RPFM lets you rebind every shortcut from PackFile → Settings → Shortcuts (the Preferences dialog has a Shortcuts button at the bottom). The dialog itself is the standard KDE KShortcutsDialog, which groups every action by collection and flags conflicts within the same scope.

The list below shows the defaults as defined in the source. Most shortcuts are configurable per scope, so a binding may legitimately fire in one widget (e.g. the table editor) and be unbound in another.

The command palette is the universal fallback. Press Ctrl+P (file palette) or Ctrl+Shift+P (command palette) and type the name of what you want. If the action exists, you can launch it from there.

Pack Menu

ActionDefault
New PackCtrl+N
Open PacksCtrl+O
Open & Merge PacksCtrl+Shift+O
Save All PacksCtrl+S
Save Current PackCtrl+Shift+S
Install PackCtrl+Shift+I
Uninstall PackCtrl+Shift+U
Load All CA PacksCtrl+G
SettingsCtrl+,
Quit(unbound)

Save All Packs (Ctrl+S) saves every open Pack; Save Current Pack (Ctrl+Shift+S) saves only the Pack the focused file/tab belongs to. Save Pack As is exposed via the Pack root context menu and the file-tab context menu rather than as a global shortcut.

File tabs

ActionDefault
Close TabCtrl+W
Previous TabCtrl+Shift+Tab
Next TabCtrl+Tab
Toggle Quick Notes(unbound)
Import From Dependencies(unbound)
Move to Other Pane(unbound)
Merge Panes(unbound)

View menu

ActionDefault
Toggle Global SearchCtrl+Shift+F
Toggle Pack Contents(unbound)
Toggle Diagnostics(unbound)
Toggle Dependencies(unbound)
Toggle References(unbound)

Command palette

ActionDefault
Open file paletteCtrl+P
Open command paletteCtrl+Shift+P

Pack tree (context-scoped)

ActionDefault
Add FileCtrl+A
Add FolderCtrl+Shift+A
Add From PackCtrl+Alt+A
New FolderCtrl+F
New DBCtrl+D
New LocCtrl+L
New TextCtrl+T
New Quick FileCtrl+Q
New AnimPack / Portrait Settings(unbound)
Merge FilesCtrl+M
DeleteDel
ExtractCtrl+E
Rename / MoveCtrl+R or F2
CopyCtrl+Shift+C
CutCtrl+X
PasteCtrl+V
DuplicateCtrl+Shift+D
Open with DecoderCtrl+J
Open with External ProgramCtrl+K
Open Pack NotesCtrl+Y
Expand AllCtrl++
Collapse AllCtrl+-

Table editor (DB / Loc)

ActionDefault
Add RowCtrl+Shift+A
Insert RowCtrl+I
Delete RowCtrl+Del
Delete Filtered-Out RowsCtrl+Shift+Del
Clone & Insert RowCtrl+D
Clone & Append RowCtrl+Shift+D
CopyCtrl+C
Copy as LUA TableCtrl+Shift+C
PasteCtrl+V
Paste as New RowCtrl+Shift+V
Rewrite SelectionCtrl+Y
Invert SelectionCtrl+-
SearchCtrl+F
Undo / RedoCtrl+Z / Ctrl+Shift+Z
Smart DeleteDel
Find References / Go To Definition / Go To File / Patch Columns / Generate IDs / Import TSV / Export TSV / etc.(unbound)

Decoder

ActionDefault
Move Field UpCtrl+Up
Move Field DownCtrl+Down
Move Field LeftCtrl+Left
Move Field RightCtrl+Right
Delete FieldCtrl+Del
Delete DefinitionCtrl+Del
Load DefinitionCtrl+L

Text editor

The text editor is a KTextEditor widget and honours your KDE keyboard configuration. RPFM removes its built-in file-save and file-save-as actions and re-binds save into the Pack via Ctrl+S. Find / replace use KTextEditor’s standard Ctrl+F / Ctrl+R. Everything else is KTextEditor-standard.

Customising

The Shortcuts dialog (PackFile → Settings → Shortcuts) shows every action grouped by collection (Pack Menu, View Menu, Pack Tree Context Menu, Table Editor, Decoder, etc.). Click a binding to edit it; conflicts within the same scope are flagged.

Bindings are persisted via KDE’s standard KActionCollection::writeSettings/readSettings, not in a single RPFM-owned file. On Linux that typically lands in ~/.config/<orgname>rc. To reset, open the Shortcuts dialog and use its built-in Defaults action.

Supported games

RPFM supports every Total War since Empire, with depth that varies depending on how interesting the game is to mod and how much the format has been reverse-engineered.

GameGame keyEditingNotes
Total War: Shogun 2 Complete Editionshogun_2_complete_editionFull
Total War: Pharaoh – Dynastiespharaoh_dynastiesFull
Total War: PharaohpharaohFull
Total War: Warhammer 3warhammer_3FullFirst game with twad_key_deletes (patch 6.3+).
Total War Saga: TroytroyFull
Total War: Three Kingdomsthree_kingdomsFull
Total War: Warhammer 2warhammer_2Full
Total War: WarhammerwarhammerFull
Total War Saga: Thrones of Britanniathrones_of_britanniaFull
Total War: AttilaattilaFull
Total War: Rome 2rome_2Full
Total War: Shogun 2shogun_2Full
Total War: NapoleonnapoleonFullNo Assembly Kit ever shipped — RPFM uses archived AK definitions.
Total War: EmpireempireFullNo Assembly Kit ever shipped — RPFM uses archived AK definitions.
Total War: ArenaarenaRead-onlyDiscontinued; RPFM can browse Arena Packs but won’t save them.

Game-specific quirks

A few rough edges worth knowing about:

  • Compression of DB tables is only safe on Warhammer 3 and newer; older games crash on compressed table data, so the compression code skips DB tables for them. Pharaoh / Pharaoh Dynasties / Three Kingdoms / Troy / Warhammer 2 still support Lzma1 for non-table files. Warhammer 3 adds Lz4 and Zstd on top of Lzma1. Shogun 2 Complete Edition supports only Lz4Block, its own raw LZ4 variant for PFH3 Packs. Anything else older than Warhammer 1 supports no compression at all.
  • twad_key_deletes is Warhammer 3 (6.3+) only. See Datacores.
  • Animation ID lookups for Three Kingdoms, Troy, Warhammer 2 and Warhammer 3 use the per-game anim_ids_*.csv shipped with the schemas repo.
  • Group formations were rebuilt for Warhammer 3 and adopt different on-disk formats per game; RPFM keeps each variant straight.
  • MatchedCombat has different decode paths for Three Kingdoms vs Warhammer 3 vs the rest.

What “full” support means

For “Full” games, RPFM can:

  • Open and save Packs of every type the engine accepts.
  • Decode and re-encode every commonly-edited file format (DB, Loc, Text, AnimPack, Animations, Portrait Settings, Atlas, Group Formations).
  • Run diagnostics, global search, references, dependency analysis.
  • Drive the optimiser, the translator, and (where applicable) startpos build, map packing, and SiegeAI patching.

Some less-common formats (BMD, ESF, RigidModel) are read-only or partially supported across all games — see Editors overview for the per-format support level.

Vanilla data lookups

For every game with a configured install path, RPFM builds a dependency cache so that vanilla data is available for reference lookups, ITM detection, the optimiser, and more. The cache is per-game and needs regenerating when the game gets a content patch.

Schemas & patches

Schemas describe the binary layout of every supported DB table for every supported game. Without them, RPFM has no idea what a units_tables/data file looks like — it’s a binary blob.

Where schemas come from

Schemas live in the rpfm-schemas repository. About → Check Updates pulls the latest version from upstream over Git as part of the unified update check (it also covers the program itself, lua autogen and the legacy AK definitions). The Git update path is gated behind the integration_git Cargo feature, which is enabled by default in the shipped UI build.

Each schema is a RON file: schema_<game>.ron. Plus:

  • anim_ids_<game>.csv — per-game animation ID maps for matched_combat / anim_fragment_battle.

When to update schemas

When RPFM says there’s a new schema update available. There’s no benefit on using older versions of the schemas.

Patches

Schemas describe what a table is, and a large part of their information is extracted automatically from the Assembly Kit of the games. Patches are manually generated corrections for the schemas: lookup overrides, default values, tooltips, type-display hints, etc. They live alongside the schemas and overlay the base schema at runtime.

To contribute a patch upstream so everyone benefits, PR your changes to rpfm-schemas. The repo’s README covers the file format.

Adding or fixing a schema

When a game patch changes a table layout (new columns, reorder, type change), the existing schema is missing the new definitions and the tables won’t decode. Fix it:

  1. Open one of the affected files in the DB Decoder.
  2. Adjust the column types until the first row decoded field shows sane values.
  3. Save the definition into the schema.
  4. Try to open the table again and see if it opens correctly.
  5. PR your changes to rpfm-schemas.

API surface

If you’re consuming schemas from Rust, the rpfm_lib::schema module is the entry point. Key types:

  • Schema — the full per-game schema document.
  • Definition — a single table version’s definition.
  • Field and FieldType — column metadata.

Telemetry & crash reports

RPFM ships two pieces of opt-out telemetry, both controlled from Preferences → Telemetry:

  • Enable Usage Telemetry — anonymous counters of which actions get used, sent to PostHog. This channel also carries any user feedback you submit.
  • Enable Crash Reports — automatic upload of panic reports and breadcrumbs to Sentry when RPFM crashes.

Both default to on. Both take effect immediately.

What gets sent

Usage telemetry

When enabled, RPFM increments per-action counters in memory and flushes them to PostHog (one event per distinct action, with the invocation count carried as a property) when the program shuts down gracefully (or, on the server side, when a session disconnects). The events are tagged with a source so server and UI activity can be told apart.

Each counter is just (action_name, count). There’s no payload, no path, no Pack name, no game, no persistent user identifier — just “Open PackFile happened 7 times in this session”. The distinct_id attached to each event is ephemeral: it’s regenerated every run from the process id and a timestamp, so it can’t be used to track you across sessions.

Examples of action names that actually appear in the codebase: Open PackFile Menu, Open PackedFile Full, Save Pack, Diagnostics Check, Cascade Edition, Optimize PackFile, Open In External Program, Merge Tables. They’re whatever string a rpfm_telemetry::track_action(...) call passes — there’s no central registry.

User feedback

RPFM has a User Feedback dialog you can use to send the developers a short message directly from the app. Submissions are posted to PostHog as a dedicated user_feedback event carrying your text. It rides on the same plumbing as usage telemetry, so sending feedback requires Enable Usage Telemetry to be on. Only what you type in the dialog is sent.

Crash reports

When enabled, RPFM:

  • Captures panics as they happen.
  • Writes a local crash report under the error/ folder.
  • Uploads the crash report to Sentry, along with breadcrumbs (a structured trail of recent operations in the session).
  • Tracks session start / session end so we can tell how often crashes occur per session.

Crash reports include the crashing thread’s stack trace, RPFM’s version, the OS family, and the breadcrumb trail. They do not include the contents of any open Pack, file paths, or anything else identifying.

In debug builds the Sentry guard is created with an empty DSN, so panics dump locally but don’t upload anywhere.

Local crash reports

Even with crash report uploads disabled, RPFM still writes panic reports locally under <config>/error/ as TOML files. They’re useful for filing bug reports yourself: when something crashes, look in error/, attach the file to a GitHub issue, and that gives the developers exactly what an automatic upload would have.

API surface

If you’re embedding rpfm_telemetry into your own RPFM-derived tool, the rpfm_telemetry API docs cover the full surface. The README in the crate has more on the design.

Building from source

If you want to compile RPFM yourself — to contribute, to enable optional features, or to run the latest develop — these are the per-platform instructions.

Windows

You’ll need:

  • Windows SDK.
  • Visual Studio Community 2022 with the MSVC C++ build tools workload.
  • Rust 1.89 (or newer) with the MSVC toolchain.
  • Craft from KDE.

Once Craft is installed, install RPFM’s KDE / Qt dependencies:

craft -i libs/qt6/qtimageformats
craft -i kimageformats
craft -i kwidgetsaddons
craft -i ktexteditor
craft -i kiconthemes
craft -i breeze-icons

KDE designer plugins

By default Craft builds KDE Frameworks with -DBUILD_DESIGNERPLUGIN=OFF. RPFM uses .ui templates with KDE widgets (KLineEdit, KComboBox, KMessageWidget, …) that are loaded at runtime via QUiLoader. Without the designer plugins, QUiLoader cannot instantiate these widget types and RPFM crashes on startup.

Edit the Craft blueprint at CraftRoot/etc/blueprints/locations/craft-blueprints-kde/kde/frameworks/frameworks.py and change BUILD_DESIGNERPLUGIN=OFF to BUILD_DESIGNERPLUGIN=ON:

self.subinfo.options.configure.args += [
    "-DBUILD_DESIGNERPLUGIN=ON",
    "-DBUILD_PYTHON_BINDINGS=OFF",
]

Then rebuild the relevant frameworks:

craft --fetch --unpack --configure --compile --install --qmerge kcompletion
craft --fetch --unpack --configure --compile --install --qmerge kwidgetsaddons

You should see kcompletion6widgets.dll and kwidgetsaddons6widgets.dll in CraftRoot/plugins/designer/.

Building

Open Craft’s terminal, move to RPFM’s source folder and run:

# Debug build:
cargo build

# Run rpfm_ui in debug mode (auto-builds rpfm_server first):
cargo run --bin rpfm_ui

# Release build:
cargo build --release

You can make any editor inherit Craft’s environment (and so be able to compile RPFM) by launching it from Craft’s terminal.

Linux

You’ll need:

  • CMake.
  • Rust 1.89 (or newer).
  • Qt6.
  • KDE Frameworks 6: KCompletion, KIconThemes, KTextEditor, KXmlGui, KWidgetsAddons.
  • xz, p7zip.

Then from the repo root:

# Debug build:
cargo build

# Run rpfm_ui in debug mode:
cargo run --bin rpfm_ui

# Release build:
cargo build --release

macOS

There’s no maintained macOS build. The qt_* Qt6 bindings RPFM uses build on macOS in principle, but nobody is currently producing or testing macOS builds. Contributions welcome.

Feature flags

Some features are gated behind cargo feature flags. Pass them with --features:

cargo run --bin rpfm_ui --features "enable_tools,support_uic"

Available flags (the up-to-date list lives in each crate’s Cargo.toml):

FlagDefaultWhat it enables
enable_toolsyesCompiles the integrated Tools menu (Translator, Faction Painter, Unit Editor).
support_model_rendererno3D RigidModel preview. Requires the renderer library on the link path.
support_uicnoPropagates UIC parsing support through rpfm_lib.
strict_subclasses_compilationnoForces a compilation failure if qt_rpfm_subclasses fails to build.
only_for_the_bravenoExposes experimental features in the About dialog.

Building the manual

To build this manual locally:

# Install mdbook + the langtabs preprocessor RPFM uses:
cargo install --locked mdbook mdbook-langtabs

# Build the docs and open them in a browser:
mdbook build --open

To build the full site (landing page + manual + cargo doc API reference), use the orchestration script in the repo root:

./build_site.sh                # full build
./build_site.sh --skip-api     # manual + landing only
./build_site.sh --skip-manual  # landing + API only

# Preview locally:
python3 -m http.server -d out 8000

Troubleshooting

  • Missing dll errors on Windows — You’re either not launching everything from Craft’s terminal, or you’re missing dependencies.

For anything else, the issue tracker is the right place.