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_VP8format. - 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
- Installation — get RPFM on your machine.
- First-time configuration — point RPFM at your games so the smart features can do their job.
- The main window — orient yourself in the UI.
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
- Download the latest
rpfm-vX.Y.Z-x86_64-pc-windows-msvc.zipfrom the releases page. - 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.
- 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. Allowrpfm_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
/dataand/contentdirectories. 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:
- Install the Assembly Kit for the game from Steam (look for “Assembly Kit” or “Mod Tools” in the game’s tools list).
- In Preferences → Paths, set the Assembly Kit path for the game.
- 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
- Menu bar. Top of the window. Houses every global action — see below.
- Tab bar. Sits under the menu bar. One tab per open file.
- 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.
- 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.
- Editor area. The big central region. Whatever tab is active here drives most of what you can do.
- 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.
- 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
| Menu | What it covers |
|---|---|
| Pack | Pack 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. |
| MyMod | The 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?. |
| View | Toggle the bottom and side docks (Diagnostics, Global Search, References, Quick Notes, Dependencies). |
| Game Selected | Switch the active Total War game, launch it, open its data / Assembly Kit folders, open RPFM’s config folder, and regenerate the dependencies cache. |
| Tools | Integrated tools that span multiple files: Translator, Faction Painter, Unit Editor. |
| About | Manual, repo, Patreon, Discord, version info, check for updates. |
| Debug | Hidden 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:
| Action | When 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 Packs | Open 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
| Action | Effect |
|---|---|
| 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 All | Save 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.
Navigating
- 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) orCtrl(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
scriptsfolder.
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:
- 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.
- 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:
- 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).
- In your new Pack, open the Dependency Manager and declare any parent mods you depend on.
- 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
- Select one or more files/folders in the Pack tree.
- Right-click and open the Run Script submenu. It lists every script currently in your
scriptsfolder (or No scripts found if the folder is empty). - Pick a script. RPFM runs it and reads the results back into the Pack.
How a script is run
- 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.
- Your script is launched with the extracted file paths as arguments (
python <script> <paths...>orlua <script> <paths...>). - 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:
- Runs the top-level code of the test file, creating the factions, regions and characters it sets up.
- Loads the game’s script libraries, then the mods of your open Packs (
script/campaign/mod/), like the game does. - Triggers
NewSession,WorldCreated,UICreatedandFirstTickAfterWorldCreated, so your mods’ listeners are registered and their first-tick code has run. - 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
| Function | What 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
| Function | What 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:
WorldStartRound.FactionRoundStart, for every faction.- For each faction:
FactionTurnStart,RegionTurnStartfor each of its regions,CharacterTurnStartfor each of its characters,FactionBeginTurnPhaseNormal,FactionAboutToEndTurn,CharacterTurnEndfor each character,RegionTurnEndfor each region, andFactionTurnEnd.
Only the regions and characters given to the faction’s region_list and character_list get their events.
Checking the results
| Function | What 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
printorout. 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_modare recorded but don’t change whattreasuryreturns, 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.subandstring.findcount characters instead of bytes, andstring.finddoesn’t use patterns, like in the game. The original versions are available asstring.len_lua,string.sub_luaandstring.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
.packfile 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 type | Editor chapter | UI today |
|---|---|---|
| DB table | DB tables | Full grid editor |
| Loc | Loc files | Full grid editor |
| Text & scripts | Text & scripts | KTextEditor (full) |
| Image (DDS, PNG…) | Images & DDS | Viewer only (replace via tree) |
| Video (CA_VP8) | Video | Metadata + IVF round-trip |
| AnimPack | AnimPack | Inner tree + per-file editors |
| AnimsTable | Animations | JSON debug view |
| AnimFragmentBattle | Animations | Structured form view |
| MatchedCombat | Animations | JSON debug view |
| Portrait Settings | Portrait Settings | Full structured editor |
Audio (.wav, .ogg…) | Audio | Play/stop player |
| RigidModel | RigidModel | Metadata + glTF export (read-only) |
| UnitVariant | Specialised editors | Full structured editor |
| Atlas | Specialised editors | Full grid editor |
| ESF (saves, startpos) | Specialised editors | Tree editor (debug-gated) |
| BMD | Specialised editors | JSON text editor |
| UIC | Specialised editors | Read-only text dump |
| Group formations | Specialised editors | Full 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+Fopens 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+Tabmove between columns;Entercommits.- 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:
| Type | Editor |
|---|---|
| Boolean | Checkbox in the cell. |
| Integers/floats | Numeric editor with format validation; rejects bad input. |
| String | Inline text editor. |
| ColourRGB | Inline RGB editor + colour picker popup. |
| Reference (FK) | Auto-completing combo box pulling values from the referenced table. |
| Sequence | Sub-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.
- Select the cell whose key you want to rename.
- Right-click → Cascade Edition.
- RPFM finds every reference to the old key across the active Pack and its parents, and asks for confirmation.
- 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.
- About → Check Updates to pull the latest schemas (along with the program, lua autogen, and old-AK updates).
- 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:
- Open the mod in RPFM.
- 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:
- Extract it from the Pack (Pack tree → Extract).
- Open it in your image editor of choice (GIMP, Photoshop, Krita…).
- Save it back to disk.
- 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 (
v0orv1). - 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:
- Convert the file to IVF (button in the editor toolbar).
- 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 - 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 likecampaign_character_arts_tables.art_set_idreference. 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_idisn’t declared incampaign_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(orvariant_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
.bnkfrom 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_rendererCargo 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_rendererin 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
.esffalls 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:
| Format | Editor chapter |
|---|---|
| AnimPack | AnimPack |
| AnimsTable | Animations |
| AnimFragmentBattle | Animations |
| MatchedCombat | Animations |
.anim raw stream | No 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.
SequenceU32columns are recursive: au32count 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):
| Category | Sample checks |
|---|---|
| Tables | Outdated 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-level | Invalid Pack name, invalid file names, missing loc data for referenced keys, ITM (identical-to-master) files, files overwriting vanilla unintentionally, duplicate files. |
| Portrait Settings | Art sets / variants not declared in the corresponding tables, missing texture references, datacored portrait_settings files. |
| Group Formations | Formations 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. |
| AnimFragmentBattle | Missing locomotion graphs, missing animation files, missing sound files, missing metadata file. |
| Text | Lua 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. |
| Dependencies | Pack declares a parent that doesn’t exist on disk. |
| Config | Missing 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.
Triggering a reference search
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, orAssKitFiles. - 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
.packfiles 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-projectin the folder. - Create VSCode Project — drops
.vscode/extensions.jsonin the folder, recommending the Lua and Code Runner extensions. - When either is enabled, RPFM also expects a
.luarc.jsonto live there (auto-added to the import-ignore list).
- Create Sublime Text Project — drops a
- Create Git Repository With GitIgnore group (checkable) — when enabled, RPFM
git inits the folder and writes a.gitignore. You can either type your own.gitignorecontents 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:
- Creates
<MyMod base>/<game key>/<name>/on disk. - Initialises
.git,.gitignore,.vscode/,<name>.sublime-projectas opted in. - Creates an empty Pack and saves it as
<name>.packinside the folder. - Stores the import-ignore list in the Pack’s settings.
- Switches the active Game Selected to match the MyMod’s game.
- 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
- 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.
- 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.
- Generate the translated loc. When you save, the Translator writes a translated
.locfile into the Pack at the right path:text/!!!!!!translated_locs.locfor Warhammer 1 and newer (except Thrones of Britannia), ortext/localisation.locfor Thrones of Britannia and older games. The translation works in-game immediately. - Persist the translation as JSON. The translation is also persisted to
<config>/translations_local/<game>/<pack>/<SOURCE>-<LANGUAGE>.json(or<LANGUAGE>.jsonfor format0). 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:
- Change the game’s language to the source language in Steam (Properties → General → Language) and let Steam download it.
- Open the Translator with that language as source. RPFM saves its vanilla texts and tells you when it’s done.
- 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:
- 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.
- 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.
- 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.
- 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
outdatedlines 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
- Open the Pack you want to add faction colour overrides to.
- Open Faction Painter.
- Pick the faction in the list.
- Adjust the colours (or hit Restore Vanilla Values to baseline first).
- 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_tablesrow. - The
land_units_tablesrow. - The unit’s variant mesh references (
unit_variants_tablesetc., 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:
- Open the Pack.
- Open the Loc files.
- Manually translate the loc values.
- 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:
- Open the Pack.
- Export every Loc to TSV.
- Use a translation tool to work on the TSV.
- Import the locs back.
- 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:
- Open the Pack you want to translate.
- Tools → Translator.
- Translate the lines marked Needs Retranslation.
- Hit Accept.
- 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
| Column | Meaning |
|---|---|
| Key | Loc key |
| Needs Retranslation? | Checked when the line needs work — either it’s new, or the source text has changed since the previous translation. |
| Removed | The line is no longer in the mod. The translator hides these by default. |
| Original Value | The source text (usually English). |
| Translated Value | Your 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
- OpenAI:
- 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(ortext/localisation.locfor older games), so the translation works in-game immediately. - On disk, as
<RPFM config>/translations_local/<game>/<pack>/<SOURCE>-<LANGUAGE>.json(or<LANGUAGE>.jsonfor format0). 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
.xmlfiles inmap/folders, remove unused.xmlfiles in the prefab folder, Remove.agffiles, Remove.model_statisticsfiles (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:
@tablecontains new rows and rows intended to overwrite vanilla. To change a vanilla unit’s stats, you put the modified row here.ztablecontains 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:
| Column | Description |
|---|---|
table_name | The table to delete a row from. |
key | The 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:
- Right-click your Pack root → Optimize PackFile (or Save Pack For Release if you also want the optimiser to save the result).
- In the optimiser dialog, tick Import datacores into
twad_key_deletesunder the Table section. - 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:
- Right-click your Pack → Create ▸ New DB, type
twad_key_deletes_tablesas the table name, and create it once. Don’t edit it manually. - Open the source table (the one with the rows you want gone).
- Select the rows.
- Right-click → Add Selection to Key Deletes → pick your
twad_key_deletestable.
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_unitsrow that’s still referenced byunits_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 validatetwad_key_deletesreferences. 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:45127by 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_libandrpfm_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:
| Endpoint | Method | Purpose |
|---|---|---|
/ws | GET | WebSocket upgrade. Carries the JSON-RPC 2.0 protocol. |
/sessions | GET | REST: list every active session. Used by the UI session picker. |
/version | GET | REST: 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 — what happens on connect, disconnect, reconnect.
- WebSocket protocol — message envelope, errors, jobs and conventions.
- Methods — every method of the API.
- MCP interface — what tools the MCP endpoint exposes.
- Client example — a concrete TypeScript / C# client implementation.
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
-
Connect. A client opens
/wswithout a session ID. The server allocates a newSessionId, spins up a background thread, and immediately sends asession.connectednotification so the client can stash the ID for later reconnection.{ "jsonrpc": "2.0", "method": "session.connected", "params": { "session_id": 12345 } } -
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). -
Disconnect. When the WebSocket drops without a
session.disconnectrequest, 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. -
Graceful disconnect. Call
session.disconnectbefore 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" } -
Empty manager → process exit. When the last WebSocket session goes away and no other session is left, the
rpfm_serverprocess 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:
| Field | Description |
|---|---|
session_id | Unique identifier. |
connection_count | Number of active WebSocket connections to this session. |
timeout_remaining_secs | Seconds until the session is cleaned up. Only set while disconnected. |
is_shutting_down | true when the session has been marked for shutdown. |
pack_names | Names 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 withsession.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" } } }
| Code | kind | Meaning |
|---|---|---|
-32001 | pack_not_found | No open pack has that key. |
-32002 | file_not_found | The file doesn’t exist in its source. |
-32003 | not_a_table | The file isn’t a DB or Loc table. |
-32004 | schema_not_loaded | The selected game has no schema loaded. |
-32005 | dependencies_not_loaded | The dependencies of the selected game aren’t loaded. |
-32006 | definition_not_found | The schema has no definition for the table. |
-32007 | read_only | The source can’t be edited, like the game files. |
-32008 | job_not_found | No job has that ID. |
-32009 | diagnostics_not_run | diagnostics.list or diagnostics.report before any check. |
-32010 | search_not_run | search.matches, search.replace or search.report before any search. |
-32011 | not_found | Something the request names doesn’t exist. |
-32601 | method_not_found | Unknown method. |
-32602 | invalid_params | The params don’t match the method (including unknown params), or a value is invalid. |
-32603 | internal | Anything 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
keythatpack.open,pack.newandsession.statusreturn: a UUID, like0b4c5a62-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’snameis its file name, and itspathis only set if it’s saved on disk. A method’spackparam 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
sourceor afile, so they can read from anywhere. Methods changing files take apackand apath, 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 calledpath_prefix. Paths on disk have names saying what they are, likedestinationortsv_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
offsetand an optionallimit, and include thetotalamount 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 withsession.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
| Method | Description |
|---|---|
session.status | Returns the selected game, what’s loaded for it, and the open packs. |
session.configure | Replaces the settings the session runs with. |
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. |
session.set_game | Selects a game, loading its schema and, optionally, its dependencies. Runs as a job. |
dependencies.generate_cache | Generates the dependencies cache of the selected game from its files and Assembly Kit, and loads it. Runs as a job. |
dependencies.rebuild | Reloads the dependencies of the selected game, like after changing the packs the open packs depend on. Runs as a job. |
dependencies.tables | Returns the tables of the game files, and the startpos, twad and CEO tables of the schema, with their version. |
dependencies.info | Returns the files of the dependencies of the selected game. |
dependencies.table_data | Returns every decoded table of a type in the game files and the parent packs. |
Packs
| Method | Description |
|---|---|
pack.info | Returns the details of an open pack. |
pack.new | Creates a new empty pack. It has no path on disk until it’s saved with a path. |
pack.open | Opens one or more packs from disk, merged into a single one. |
pack.open_vanilla | Opens all the vanilla packs of the selected game, merged into a single one. |
pack.close | Closes an open pack, discarding unsaved changes. |
pack.close_all | Closes all open packs, discarding unsaved changes. |
pack.save | Saves an open pack to disk. |
pack.update | Changes properties of an open pack. Only the fields set are changed. |
pack.settings | Returns the settings of an open pack. |
pack.update_settings | Changes settings of an open pack. Only the keys set are changed. |
Files
| Method | Description |
|---|---|
files.list | Lists the files of a source, sorted by path. |
files.create | Creates a new empty file in an open pack. |
files.add_from_disk | Adds files and folders from disk to an open pack. |
files.copy | Copies files and folders from any source into an open pack, keeping their paths. |
files.delete | Deletes files and folders from an open pack. |
files.rename | Renames or moves files and folders of an open pack. |
files.duplicate | Copies files of an open pack in the same pack, adding a number to their names. |
files.extract | Extracts files and folders of an open pack, the game files or the parent packs to disk. |
file.read | Returns the contents of a file of any source. |
file.write | Replaces the contents of a file of an open pack. |
animpack.list | Lists the files inside an AnimPack of any source. |
animpack.add | Copies files of an open pack into an AnimPack of an open pack. |
animpack.extract | Copies files of an AnimPack of any source into an open pack. |
animpack.delete | Deletes files from an AnimPack of an open pack. |
file.view_data | Returns a file decoded as a client opening it in a view needs it. |
files.info | Returns the info of files of an open pack, like their original pack and last modification time. |
files.paste | Copies or moves files and folders of open packs into a folder of an open pack. |
files.from_all_sources | Returns files and folders found in the open packs, the parent packs and the game files. |
files.save_files | Adds files to an open pack, replacing the ones with the same path, optionally optimizing the pack after it. |
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. |
file.save_external | Replaces a file of an open pack with the file on disk an external program edited. |
Tables
| Method | Description |
|---|---|
table.info | Returns the definition and row count of a table. |
table.rows | Returns rows of a table, optionally filtered and with only some columns. |
schema.definition | Returns the columns of a table as defined in the schema. |
table.edit | Edits rows of a DB or Loc table in an open pack. |
table.column_values | Returns the distinct values of a column of a table, sorted. |
table.unused_numbers | Returns numbers no row uses yet in an integer column of a table, for new rows needing unique ones, like ids. |
table.merge | Merges tables of the same type of an open pack into a new one. |
table.upgrade | Updates a table of an open pack to the version it has in the game files. |
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. |
table.add_key_deletes | Adds rows to a key deletes table of an open pack, to delete keys of a table in the game. |
table.export_tsv | Writes a table to a TSV file. |
table.import_tsv | Replaces a table of an open pack with the contents of a TSV file, keeping its GUID. |
Schema
| Method | Description |
|---|---|
schema.tables | Lists the tables of the schema, with the versions it has definitions for. |
schema.patch_column | Changes how the schema describes a column, with a local patch. |
schema.remove_patches | Removes the local patches of a table, or of one of its columns, and reloads the schema. |
schema.update | Downloads the latest schemas, reloads the selected game’s one, and rebuilds the dependencies. Runs as a job. |
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. |
schema.raw_definitions | Returns definitions of a table as the schema stores them, to edit them. |
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. |
schema.delete_definition | Removes a definition from the schema of the selected game, then saves and reloads the schema. |
schema.referencing_columns | Returns the columns of other tables referencing each column of a table. |
schema.patches | Returns the patches applied to the columns of a table. |
schema.import_patches | Adds patches to the schema of the selected game itself, and saves it. |
schema.missing_definitions | Returns the tables of an open pack with rows that can’t be decoded with the schema. |
References
| Method | Description |
|---|---|
references.definition | Finds the row where a value of a referenced table is defined. |
references.usages | Finds the rows of other tables referencing a value of a table. |
references.loc | Finds the row of a Loc file with a key. |
references.loc_source | Returns the table, column and row a loc key belongs to. |
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. |
references.table_data | Returns the values each column of a table can reference, with their lookups. |
references.values | Returns the values a reference column of a table can have, with their display text. |
Diagnostics
| Method | Description |
|---|---|
diagnostics.run | Checks the open packs for problems, keeping the results for diagnostics.list. Runs as a job. |
diagnostics.list | Returns results of the last check, optionally filtered. |
diagnostics.report | Returns the results of the last diagnostics check, as the check returns them. |
diagnostics.ignore | Makes the next checks of a pack skip some results. Saved in the pack’s settings. |
Search
| Method | Description |
|---|---|
search.report | Returns the last search with all its matches, as the search returns them. |
search.run | Searches text, keeping the matches for search.matches. Runs as a job. |
search.matches | Returns matches of the last search, optionally filtered. |
search.replace | Replaces matches of the last search, and searches the edited files again. |
Notes
| Method | Description |
|---|---|
notes.list | Returns the notes of an open pack for a path. |
notes.add | Attaches a note to a file or folder of an open pack. |
notes.delete | Deletes a note of an open pack. |
Tools
| Method | Description |
|---|---|
tools.optimizer_options | Returns the optimizer options, as the server’s settings have them. |
tools.optimize | Removes data of an open pack that’s identical to the vanilla one, or unneeded. Runs as a job. |
tools.patch_siege_ai | Patches the siege maps of an open pack, so the AI can use them. Warhammer games only. |
tools.pack_map | Adds the tiles and tile maps of a map exported by Terry to an open pack. |
tools.generate_missing_locs | Adds empty loc entries for the localised columns of the tables of the open packs that have none. |
tools.update_anim_ids | Offsets the animation ids of an open pack, like after a game update moves them. |
tools.anims_by_skeleton | Returns the paths of the animations using a skeleton, in the open packs and the dependencies. |
tools.export_gltf | Exports a RigidModel to a glTF file, with its textures. |
tools.set_video_format | Changes the format of a ca_vp8 video of an open pack. |
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. |
tools.init_mymod | Creates the folder of a new MyMod, with editor configs for Lua scripting. |
lua.run_tests | Runs Lua tests against the scripts of the game and the open packs, outside of the game. Runs as a job. |
startpos.campaigns | Returns the campaigns a startpos can be built for. |
startpos.start | Prepares the Assembly Kit to build a startpos with the tables of an open pack, and launches the game to build it. |
startpos.finish | Imports the startpos built by the game into the pack, or cancels the build, and cleans up the build 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. |
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. Runs as a job. |
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. Runs as a job. |
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. Runs as a job. |
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. |
tools.plugin_scripts | Returns the plugin scripts in the scripts folder of the config. |
tools.run_plugin_script | Runs a plugin script over files of an open pack, reading back the files it changes. |
lua.hovers | Returns the docs of the game’s Lua API for the symbols of a Lua script, to show on hover. |
ceo.traits | Returns the trait CEOs of the Assembly Kit tables. |
ceo.add_entries | Adds CEO entries (armour, career, traits and their locs) to an open pack. |
ceo.build | Builds ceo_data.ccd in the Assembly Kit from the CEO tables of an open pack, running BOB. |
ceo.import | Imports the ceo_data.ccd built by ceo.build into an open pack. |
Translations
| Method | Description |
|---|---|
translations.pack | Returns the whole translation of an open pack to a language, as the translator works with it. |
translations.submit | Submits the saved translation of a pack to the Translation Hub, opening or updating a pull request. |
translations.list | Returns the translation of the texts of an open pack to a language. |
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. |
GitHub
| Method | Description |
|---|---|
github.sign_in_start | Starts a GitHub sign-in, returning the code the user has to enter on GitHub. |
github.sign_in_poll | Checks if the user approved a sign-in. Once approved, the server keeps the sign-in. |
github.account | Returns the GitHub account the user is signed in as. |
github.sign_out | Signs out of GitHub, deleting the stored sign-in. |
Updates
| Method | Description |
|---|---|
updates.check | Checks if there is an update. |
updates.apply | Downloads an update. Schemas can’t be updated with this, as they need reloading: use schema.update. |
Jobs
| Method | Description |
|---|---|
job.status | Returns the state of a job. |
job.wait | Waits for a job to end, and returns its state. |
job.cancel | Cancels 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)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the selected game, like warhammer_3. |
schema_loaded | boolean | yes | If a schema is loaded for the selected game. Tables can’t be read without one. |
dependencies | DependenciesStatus | yes | What’s loaded of the dependencies of the selected game. |
packs | array of PackSummary | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
settings | any | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the game, like warhammer_3. |
rebuild_dependencies | boolean | no, default true | If the dependencies (vanilla files, Assembly Kit tables, parent packs) are loaded for the game. |
Result (SessionStatus)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the selected game, like warhammer_3. |
schema_loaded | boolean | yes | If a schema is loaded for the selected game. Tables can’t be read without one. |
dependencies | DependenciesStatus | yes | What’s loaded of the dependencies of the selected game. |
packs | array of PackSummary | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
ignore_game_files_in_assembly_kit | boolean or null | no | If tables in the game files are skipped when reading the Assembly Kit. Defaults to the server’s setting. |
Result (SessionStatus)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the selected game, like warhammer_3. |
schema_loaded | boolean | yes | If a schema is loaded for the selected game. Tables can’t be read without one. |
dependencies | DependenciesStatus | yes | What’s loaded of the dependencies of the selected game. |
packs | array of PackSummary | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
only_parent_packs | boolean | no, default false | If only the parent packs are reloaded, instead of the whole dependencies. |
Result (SessionStatus)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the selected game, like warhammer_3. |
schema_loaded | boolean | yes | If a schema is loaded for the selected game. Tables can’t be read without one. |
dependencies | DependenciesStatus | yes | What’s loaded of the dependencies of the selected game. |
packs | array of PackSummary | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
tables | map of integer | yes | Version 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
Result (DependencyTableData)
| Name | Type | Required | Description |
|---|---|---|---|
tables | array of any | yes | The tables. |
Types
Type DependenciesStatus
What’s loaded of the dependencies of the selected game.
| Name | Type | Required | Description |
|---|---|---|---|
vanilla_loaded | boolean | yes | If the vanilla files of the game are loaded. |
assembly_kit_loaded | boolean | yes | If the tables of the game’s Assembly Kit are loaded. |
parent_files | integer | yes | Amount of files loaded from the parent packs of the open packs. |
error | string or null | no | Why 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.
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
file_count | integer | yes | Amount of files in the pack. |
Packs
Methods about the open packs.
pack.info
Returns the details of an open pack.
Params (GetPackInfo)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
Result (PackDetails)
| Name | Type | Required | Description |
|---|---|---|---|
version | string | yes | Version of the pack format, like PFH5. |
compression | string | yes | Compression format of the files of the pack, like None or Lz4. |
index_encrypted | boolean | yes | If the index of the pack is encrypted. |
data_encrypted | boolean | yes | If the data of the pack is encrypted. |
index_includes_timestamp | boolean | yes | If the index of the pack includes the timestamp of each file. |
extended_header | boolean | yes | If the pack has the extended header of some Arena packs. |
timestamp | integer | yes | Unix time of the last time the pack was saved. |
dependencies | array of PackDependency | yes | Packs this pack depends on, loaded as parent files. |
operational_mode | any | yes | If the pack is in normal mode ("Normal") or MyMod mode ({"MyMod": [game_folder, mod_name]}). |
file_count | integer | yes | Amount of files in the pack. |
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
path | string or null | yes | Path 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)
| Name | Type | Required | Description |
|---|---|---|---|
name | string or null | no | File 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)
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
file_count | integer | yes | Amount of files in the pack. |
pack.open
Opens one or more packs from disk, merged into a single one.
Params (OpenPack)
| Name | Type | Required | Description |
|---|---|---|---|
paths | array of string | yes | Paths 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_loading | boolean or null | no | If file data is read from disk only when needed. Defaults to the server’s setting. |
Result (PackSummary)
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
file_count | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
file_count | integer | yes | Amount of files in the pack. |
pack.close
Closes an open pack, discarding unsaved changes.
Params (ClosePack)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string or null | no | Absolute path to save the pack to, creating its missing folders. If not set, the pack is saved to its current path. |
clean | boolean | no, default false | If files that failed to decode are removed before saving. Use it if saving normally fails. |
disable_uuid_regeneration | boolean or null | no | If tables keep their GUID instead of getting a new one. Defaults to the server’s setting. |
allow_editing_ca_packs | boolean or null | no | If packs of the vanilla types (not Mod or Movie) can be saved. Defaults to the server’s setting. |
Result (PackSummary)
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
file_count | integer | yes | Amount of files in the pack. |
pack.update
Changes properties of an open pack. Only the fields set are changed.
Params (UpdatePack)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
pack_type | string or null | no | New type of the pack: Boot, Release, Patch, Mod or Movie. |
compression | string or null | no | New compression format of the pack, like None or Lz4. If the game doesn’t support it, a supported one is used. |
index_encrypted | boolean or null | no | If the index of the pack is encrypted. Only PFH4 and newer packs support encryption. |
data_encrypted | boolean or null | no | If the data of the pack is encrypted. Only PFH4 and newer packs support encryption. |
index_includes_timestamp | boolean or null | no | If the index of the pack includes the timestamp of each file. |
dependencies | array of PackDependency or null | no | New list of packs this pack depends on. |
operational_mode | any | no | New mode of the pack: "Normal", or {"MyMod": [game_folder, mod_name]}. |
Result (PackDetails)
| Name | Type | Required | Description |
|---|---|---|---|
version | string | yes | Version of the pack format, like PFH5. |
compression | string | yes | Compression format of the files of the pack, like None or Lz4. |
index_encrypted | boolean | yes | If the index of the pack is encrypted. |
data_encrypted | boolean | yes | If the data of the pack is encrypted. |
index_includes_timestamp | boolean | yes | If the index of the pack includes the timestamp of each file. |
extended_header | boolean | yes | If the pack has the extended header of some Arena packs. |
timestamp | integer | yes | Unix time of the last time the pack was saved. |
dependencies | array of PackDependency | yes | Packs this pack depends on, loaded as parent files. |
operational_mode | any | yes | If the pack is in normal mode ("Normal") or MyMod mode ({"MyMod": [game_folder, mod_name]}). |
file_count | integer | yes | Amount of files in the pack. |
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack.settings
Returns the settings of an open pack.
Params (GetPackSettings)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
Result (PackSettingsValues)
| Name | Type | Required | Description |
|---|---|---|---|
text | map of string | no, default {} | Multi-line text settings. |
string | map of string | no, default {} | Single-line text settings. |
bool | map of boolean | no, default {} | Boolean settings. |
number | map of integer | no, default {} | Number settings. |
pack.update_settings
Changes settings of an open pack. Only the keys set are changed.
Params (UpdatePackSettings)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
settings | PackSettingsValues | yes | Settings to set, in the format pack.settings returns them. |
Result (PackSettingsValues)
| Name | Type | Required | Description |
|---|---|---|---|
text | map of string | no, default {} | Multi-line text settings. |
string | map of string | no, default {} | Single-line text settings. |
bool | map of boolean | no, default {} | Boolean settings. |
number | map of integer | no, default {} | Number settings. |
Types
Type PackDependency
A pack another pack depends on.
| Name | Type | Required | Description |
|---|---|---|---|
enabled | boolean | yes | If the dependency is loaded. |
name | string | yes | File name of the pack. |
Type PackSettingsValues
Settings of a pack, by type of value and key, like diagnostics_files_to_ignore or disable_autosaves.
| Name | Type | Required | Description |
|---|---|---|---|
text | map of string | no, default {} | Multi-line text settings. |
string | map of string | no, default {} | Single-line text settings. |
bool | map of boolean | no, default {} | Boolean settings. |
number | map of integer | no, 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)
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Source to list. |
path_prefix | string | no, default "" | Only files whose path starts with this are listed. |
recursive | boolean | no, default true | If files in subfolders are listed. If false, the subfolders are listed instead. |
file_types | array of string or null | no | If set, only files of these types are listed, like DB, Loc, Text, Image or RigidModel. |
offset | integer | no, default 0 | Amount of files to skip. |
limit | integer or null | no | Maximum amount of files to return. Defaults to 500. |
Result (FileList)
| Name | Type | Required | Description |
|---|---|---|---|
files | array of FileEntry | yes | The files in the page. |
folders | array of string | yes | Subfolders directly under the prefix, with their full path. Only filled in non-recursive listings. |
total | integer | yes | Amount of files matching the request, in all pages. |
files.create
Creates a new empty file in an open pack.
Params (CreateFile)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the new file in the pack, like db/units_tables/my_mod or text/db/my_mod.loc. |
kind | NewFileKind | yes | Type of the new file. |
Result (FileEntry)
| Name | Type | Required | Description |
|---|---|---|---|
path | string | yes | Path of the file in its source. |
file_type | string | yes | Type 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
paths | array of string | yes | Paths on disk of the files and folders to add. |
folder | string | no, default "" | Folder of the pack to add them to. Empty for the root of the pack. |
pack_paths | array of string or null | no | Path 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_folder | boolean or null | no | If added folders keep their own name in the pack, instead of adding only their contents. Defaults to the server’s setting. |
ignore | array of string | no, default [] | Files and folders under any of these paths are skipped. |
Result (FilesAdded)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths in the pack of the added files. |
not_added | array of string | no | Paths of the files that couldn’t be added. |
error | string or null | no | Last 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)
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Where to copy the files from. |
paths | array of string | yes | Paths of the files and folders to copy. |
pack | string | yes | Key of the pack to copy them to. |
Result (FilesAdded)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths in the pack of the added files. |
not_added | array of string | no | Paths of the files that couldn’t be added. |
error | string or null | no | Last error found while adding the files, if any. |
files.delete
Deletes files and folders from an open pack.
Params (DeleteFiles)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
paths | array of string | yes | Paths of the files and folders to delete. |
Result (FilesDeleted)
| Name | Type | Required | Description |
|---|---|---|---|
deleted | array of string | yes | Paths of the deleted files. |
files.rename
Renames or moves files and folders of an open pack.
Params (RenameFiles)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
renames | array of FileRename | yes | Renames to apply. |
Result (FilesRenamed)
| Name | Type | Required | Description |
|---|---|---|---|
renamed | array of FileRename | yes | Old 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
paths | array of string | yes | Paths of the files and folders to duplicate. |
Result (FilesAdded)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths in the pack of the added files. |
not_added | array of string | no | Paths of the files that couldn’t be added. |
error | string or null | no | Last 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)
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Where the files are. The Assembly Kit tables can’t be extracted. |
paths | array of string | yes | Paths of the files and folders to extract. |
destination | string | yes | Folder on disk to extract them to. Their paths in the source are kept under it. |
as_tsv | boolean | no, default false | If tables are extracted as TSV files instead of binary ones. |
tsv_keys_first | boolean or null | no | If TSV files use the old column order, with keys first. Defaults to the server’s setting. |
Result (FilesExtracted)
| Name | Type | Required | Description |
|---|---|---|---|
extracted | array of string | yes | Paths on disk of the extracted files. |
file.read
Returns the contents of a file of any source.
Params (ReadFile)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The file. |
format | ReadFormat | no, default "decoded" | How to return the contents. Defaults to decoded. |
Result (FileContents)
| Name | Type | Required | Description |
|---|---|---|---|
file_type | string | yes | Type of the file, like Text, Image or RigidModel. |
contents | FileData | yes | The contents. |
file.write
Replaces the contents of a file of an open pack.
Params (WriteFile)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the file. With raw contents, the file is created if it doesn’t exist. |
contents | FileData | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The AnimPack. |
Result (FileList)
| Name | Type | Required | Description |
|---|---|---|---|
files | array of FileEntry | yes | The files in the page. |
folders | array of string | yes | Subfolders directly under the prefix, with their full path. Only filled in non-recursive listings. |
total | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the AnimPack. |
animpack | string | yes | Path of the AnimPack. |
source_pack | string | yes | Key of the pack to copy the files from. |
paths | array of string | yes | Paths of the files and folders to copy. |
Result (FilesAdded)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths in the pack of the added files. |
not_added | array of string | no | Paths of the files that couldn’t be added. |
error | string or null | no | Last error found while adding the files, if any. |
animpack.extract
Copies files of an AnimPack of any source into an open pack.
Params (ExtractFromAnimPack)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The AnimPack. |
paths | array of string | yes | Paths inside the AnimPack of the files and folders to copy. |
pack | string | yes | Key of the pack to copy them to. |
Result (FilesAdded)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths in the pack of the added files. |
not_added | array of string | no | Paths of the files that couldn’t be added. |
error | string or null | no | Last error found while adding the files, if any. |
animpack.delete
Deletes files from an AnimPack of an open pack.
Params (DeleteFromAnimPack)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the AnimPack. |
animpack | string | yes | Path of the AnimPack. |
paths | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
paths | array of string or null | no | Paths of the files. If not set, the info of every file of the pack is returned. Paths without a file are skipped. |
Result (FilesInfo)
| Name | Type | Required | Description |
|---|---|---|---|
files | array of any | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
sources | map of array of string | yes | Paths of the files and folders to paste, by key of their pack. |
cut | boolean | yes | If the files are removed from their packs, instead of copied. |
pack | string | yes | Key of the pack to paste them into. |
folder | string | yes | Folder of the pack to paste them into. Empty for the root of the pack. |
Result (FilesPasted)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths of the files added to the target pack. |
deleted | map of array of string | yes | Paths of the files removed from each source pack, by pack key, if they were cut. |
not_pasted | array of string | no | Paths of the files that couldn’t be pasted. Cut files that couldn’t be pasted are kept in their packs. |
error | string or null | no | Last 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)
| Name | Type | Required | Description |
|---|---|---|---|
paths | array of any | yes | Paths of the files and folders. |
lowercase_paths | boolean | no, default false | If the paths of the returned files are lowercased. |
Result (FilesFromAllSources)
| Name | Type | Required | Description |
|---|---|---|---|
files | any | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
files | array of any | yes | The files, named with their path in the pack. |
optimize | boolean | no, default false | If the pack is optimized with the optimizer options of the settings after adding the files. |
Result (FilesChanged)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths of the added or edited files. |
deleted | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the file. |
Result (ExternalFile)
| Name | Type | Required | Description |
|---|---|---|---|
path | string | yes | Path 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the file in the pack. |
external_path | string | yes | Path 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 formatfile.readreturns 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.
| Name | Type | Required | Description |
|---|---|---|---|
path | string | yes | Path of the file in its source. |
file_type | string | yes | Type of the file, like DB, Loc, Text or Image. |
Type FileRef
A file in one of the sources.
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Where the file is. |
path | string | yes | Path of the file in its source. |
Type FileRename
A rename of a file or folder.
| Name | Type | Required | Description |
|---|---|---|---|
from | string | yes | Current path. |
to | string | yes | New 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 aredb/<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, likeunits_tables.version(integerornull, 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(stringornull, no): Format of the text, likePlain,Lua,XmlorJson. Defaults toPlain.
{ "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 ofEntryCopy, 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.
| Name | Type | Required | Description |
|---|---|---|---|
from | string | yes | ID of the vanilla entry. |
to | string | yes | ID 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.rowsreturns only the rows you need, and for images and other binary filesrawis 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)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The table. |
Result (TableInfo)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables, or loc for Loc files. |
version | integer | yes | Version of the table’s definition. |
columns | array of Field | yes | Columns of the table, in the order row values are, with the schema patches applied. |
row_count | integer | yes | Amount of rows in the table. |
table.rows
Returns rows of a table, optionally filtered and with only some columns.
Params (GetTableRows)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The table. |
columns | array of string or null | no | If set, only these columns are returned, in this order. |
filters | array of RowFilter | no, default [] | Only rows matching all these filters are returned. |
offset | integer | no, default 0 | Amount of matching rows to skip. |
limit | integer or null | no | Maximum amount of rows to return. Defaults to 100. |
Result (TableRows)
| Name | Type | Required | Description |
|---|---|---|---|
columns | array of string | yes | Names of the columns of the values of each row. |
rows | array of TableRow | yes | The rows in the page. |
total | integer | yes | Amount of rows matching the filters, in all pages. |
schema.definition
Returns the columns of a table as defined in the schema.
Params (GetTableDefinition)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
version | integer or null | no | Version 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table. |
version | integer | yes | Version of the definition. |
columns | array of Field | yes | Columns 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the table in the pack. |
edits | array of RowEdit | yes | Edits to apply. |
Result (TableEdited)
| Name | Type | Required | Description |
|---|---|---|---|
row_count | integer | yes | Amount of rows the table has after the edits. |
table.column_values
Returns the distinct values of a column of a table, sorted.
Params (GetColumnValues)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like factions_tables. |
column | string | yes | Name of the column. |
include_packs | boolean | no, default true | If the tables of the open packs are included. |
include_dependencies | boolean | no, default true | If the tables of the game files and the parent packs are included. |
prefix | string | no, default "" | Only values starting with this are returned. |
offset | integer | no, default 0 | Amount of values to skip. |
limit | integer or null | no | Maximum amount of values to return. Defaults to 500. |
Result (ColumnValues)
| Name | Type | Required | Description |
|---|---|---|---|
values | array of string | yes | The values in the page, sorted. |
total | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like main_units_tables. |
column | string | yes | Name of the integer column, like unique_index. |
count | integer | no, default 1 | Amount of numbers to return, up to 10000. Defaults to 1. |
Result (UnusedNumbers)
| Name | Type | Required | Description |
|---|---|---|---|
numbers | array of integer | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
paths | array of string | yes | Paths of the tables to merge. |
merged_path | string | yes | Path of the merged table. |
delete_sources | boolean | no, default false | If the merged tables are deleted afterwards. |
delta | boolean | no, default false | If 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. |
resolutions | array of any | no, default [] | How to resolve the conflicts of a previous delta merge. |
Result (TablesMerged)
| Name | Type | Required | Description |
|---|---|---|---|
merged | string or null | yes | Path of the merged table, if the merge was done. |
conflicts | array of any | no | Rows 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the table. |
Result (TableUpgraded)
| Name | Type | Required | Description |
|---|---|---|---|
old_version | integer | yes | Version the table had. |
new_version | integer | yes | Version the table has now. |
deleted_columns | array of string | yes | Columns removed by the update. |
added_columns | array of string | yes | Columns 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
table_name | string | yes | Name of the table the key is from, like factions_tables. |
column | string | yes | Name of the key column. |
old_value | string | yes | Current value. |
new_value | string | yes | New value. |
version | integer or null | no | Version of the table’s definition. Defaults to the version in the game files, or the newest one. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
file_name | string | yes | File name of the key deletes table, under db/twad_key_deletes_tables/. |
table_name | string | yes | Name of the table the keys belong to, like units_tables. |
keys | array of string | yes | Keys to delete. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths of the edited files. |
table.export_tsv
Writes a table to a TSV file.
Params (ExportTsv)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The table. Assembly Kit tables can’t be exported. |
destination | string | yes | Path of the TSV file to write. |
tsv_keys_first | boolean or null | no | If 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the table in the pack. |
tsv_path | string | yes | Path of the TSV file to read. |
Result (TableEdited)
| Name | Type | Required | Description |
|---|---|---|---|
row_count | integer | yes | Amount of rows the table has after the edits. |
Types
Type Field
A column of a table, as the schema describes it.
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Name of the column. |
field_type | any | yes | Type 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_key | boolean | yes | If the column is part of the key of the table. |
default_value | string or null | yes | Value of the column in new rows, if it has one. |
is_filename | boolean | yes | If the column holds paths of files. |
filename_relative_path | string or null | yes | Folders the files of the column are in, separated by ;. A % in them stands for the value of the column. |
is_reference | [string, string] or null | yes | Table and column the values of this column must exist in, like ["factions", "key"]. The table name has no _tables suffix. |
lookup | array of string or null | yes | Columns of the referenced table shown next to the values of this one, to identify them. |
description | string | yes | Description of the column. Empty if it has none. |
ca_order | integer | yes | Position of the column in the Assembly Kit, or -1 if unknown. |
is_bitwise | integer | yes | If 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_values | map of string | yes | Names of the values of a column converted from an integer one, by value. |
is_part_of_colour | integer or null | yes | Group of the colour columns merged into this one, if it’s a merged colour column. |
unused | boolean | yes | If the game doesn’t use the column. |
Type FileRef
A file in one of the sources.
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Where the file is. |
path | string | yes | Path 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 aredb/<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(RowRefornull, 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(integerornull, no): Position of the new row. If not set, it’s added at the end.values(map ofany, 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 ofany, yes): New values, by column name. Columns not set keep their value.
{ "op": "delete", "indexes": … }: Removes rows.indexes(array ofinteger, 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.
| Name | Type | Required | Description |
|---|---|---|---|
column | string | yes | Name of the column. |
op | FilterOp | yes | How the value is compared. |
value | string | yes | Value to compare with. |
ignore_case | boolean | no, default false | If 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.
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The table. |
index | integer | yes | Index of the row in the table. |
Type TableRow
A row of a table.
| Name | Type | Required | Description |
|---|---|---|---|
index | integer | yes | Position of the row in the table. Used to edit it. |
values | array of any | yes | Values 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)
| Name | Type | Required | Description |
|---|---|---|---|
prefix | string | no, default "" | Only tables whose name starts with this are listed. |
Result (SchemaTables)
| Name | Type | Required | Description |
|---|---|---|---|
tables | map of array of integer | yes | Versions 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
column | string | yes | Name of the column. |
patch | map of string | yes | Values 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
column | string or null | no | If 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)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the selected game, like warhammer_3. |
schema_loaded | boolean | yes | If a schema is loaded for the selected game. Tables can’t be read without one. |
dependencies | DependenciesStatus | yes | What’s loaded of the dependencies of the selected game. |
packs | array of PackSummary | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
ignore_game_files | boolean or null | no | If tables in the game files are skipped. Defaults to the server’s setting. |
Result (SessionStatus)
| Name | Type | Required | Description |
|---|---|---|---|
game | string | yes | Key of the selected game, like warhammer_3. |
schema_loaded | boolean | yes | If a schema is loaded for the selected game. Tables can’t be read without one. |
dependencies | DependenciesStatus | yes | What’s loaded of the dependencies of the selected game. |
packs | array of PackSummary | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
version | integer or null | no | If set, only the definition of this version is returned. |
Result (RawDefinitions)
| Name | Type | Required | Description |
|---|---|---|---|
definitions | array of any | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
definition | any | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
version | integer | yes | Version of the definition. |
Result (Done)
Empty object: {}.
schema.referencing_columns
Returns the columns of other tables referencing each column of a table.
Params (GetReferencingColumns)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like factions_tables. |
version | integer or null | no | Version of the table’s definition. Defaults to the version in the game files, or the newest one. |
Result (ReferencingColumns)
| Name | Type | Required | Description |
|---|---|---|---|
columns | map of map of array of string | yes | For each referenced column, the referencing columns by table name. |
schema.patches
Returns the patches applied to the columns of a table.
Params (GetTablePatches)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like factions_tables. |
version | integer | yes | Version of the table’s definition. If the table has no definition with this version, the patches of the table are returned. |
Result (TablePatches)
| Name | Type | Required | Description |
|---|---|---|---|
patches | map of map of string | yes | Patches 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)
| Name | Type | Required | Description |
|---|---|---|---|
patches | map of map of map of string | yes | Patches, 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
Result (MissingDefinitions)
| Name | Type | Required | Description |
|---|---|---|---|
paths | array of string | yes | Paths of the tables, sorted. |
Types
Type DependenciesStatus
What’s loaded of the dependencies of the selected game.
| Name | Type | Required | Description |
|---|---|---|---|
vanilla_loaded | boolean | yes | If the vanilla files of the game are loaded. |
assembly_kit_loaded | boolean | yes | If the tables of the game’s Assembly Kit are loaded. |
parent_files | integer | yes | Amount of files loaded from the parent packs of the open packs. |
error | string or null | no | Why 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.
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key 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. |
name | string | yes | File name of the pack, like my_mod.pack. |
path | string or null | yes | Path of the pack on disk, if it’s saved there. |
pack_type | string | yes | Type of the pack: Boot, Release, Patch, Mod or Movie. |
file_count | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Referenced table, like factions_tables. |
column | string | yes | Referenced column. If it’s a localised column, the table’s key is searched instead. |
value | string | yes | Value to find. |
pack | string or null | no | If set, only this open pack is searched, instead of all of them and the dependencies. |
Result (FoundRow)
| Name | Type | Required | Description |
|---|---|---|---|
values | map of any | yes | Values of the row, by column name. |
column_index | integer | yes | Index of the column the value is in. |
path | string | yes | Path of the table in its source. |
row_index | integer | yes | Index of the row. |
source | FileSource | yes | Where 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Table the value is from, like factions_tables. |
column | string | yes | Column the value is from, like key. |
value | string | yes | Value to find usages of. |
version | integer or null | no | Version of the table’s definition, to know which columns reference it. Defaults to the version in the game files, or the newest one. |
pack | string or null | no | If set, only this open pack is searched, instead of all of them and the dependencies. |
offset | integer | no, default 0 | Amount of usages to skip. |
limit | integer or null | no | Maximum amount of usages to return. Defaults to 200. |
Result (Usages)
| Name | Type | Required | Description |
|---|---|---|---|
usages | array of Usage | yes | The usages in the page. |
total | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Key to find, like factions_screen_name_wh_main_emp_empire. |
pack | string or null | no | If set, only this open pack is searched, instead of all of them and the dependencies. |
Result (FoundRow)
| Name | Type | Required | Description |
|---|---|---|---|
values | map of any | yes | Values of the row, by column name. |
column_index | integer | yes | Index of the column the value is in. |
path | string | yes | Path of the table in its source. |
row_index | integer | yes | Index of the row. |
source | FileSource | yes | Where the table is. |
references.loc_source
Returns the table, column and row a loc key belongs to.
Params (GetLocSource)
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | The loc key, like factions_screen_name_wh_main_emp_empire. |
Result (LocSourceLookup)
| Name | Type | Required | Description |
|---|---|---|---|
source | LocSource or null | yes | Table, 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)
| Name | Type | Required | Description |
|---|---|---|---|
row | RowRef | yes | The row. |
Result (RowLocs)
| Name | Type | Required | Description |
|---|---|---|---|
locs | array of RowLoc | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like units_tables. |
version | integer | yes | Version of the table’s definition. |
regenerate | boolean | no, default false | If the references to the tables of the open packs are generated again, instead of reusing the cached ones. |
Result (TableReferenceData)
| Name | Type | Required | Description |
|---|---|---|---|
columns | map of any | yes | Referenceable 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)
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table with the reference column, like units_tables. |
column | string | yes | Name of the reference column. |
prefix | string | no, default "" | Only values starting with this are returned. |
offset | integer | no, default 0 | Amount of values to skip. |
limit | integer or null | no | Maximum amount of values to return. Defaults to 500. |
Result (ReferenceValues)
| Name | Type | Required | Description |
|---|---|---|---|
values | array of ReferenceValue | yes | The values in the page, sorted. |
total | integer | yes | Amount 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 aredb/<table_name>/ak_data.
Type LocSource
Table, column and row a loc key belongs to.
| Name | Type | Required | Description |
|---|---|---|---|
table_name | string | yes | Name of the table, like factions_tables. |
column | string | yes | Name of the localised column. |
key_values | array of string | yes | Values of the key columns of the row. |
Type ReferenceValue
A value a reference column can have.
| Name | Type | Required | Description |
|---|---|---|---|
value | string | yes | The value. |
lookup | string | no | Text 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.
| Name | Type | Required | Description |
|---|---|---|---|
column | string | yes | Name of the localised column, like onscreen_name. |
key | string | yes | Loc key of the column, like land_units_onscreen_name_my_unit. |
text | string or null | yes | Text 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.
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The table. |
index | integer | yes | Index of the row in the table. |
Type FileRef
A file in one of the sources.
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Where the file is. |
path | string | yes | Path of the file in its source. |
Type Usage
A row referencing a value.
| Name | Type | Required | Description |
|---|---|---|---|
column | string | yes | Name of the column referencing the value. |
values | map of any | yes | Values of the row, by column name. |
column_index | integer | yes | Index of the column the value is in. |
path | string | yes | Path of the table in its source. |
row_index | integer | yes | Index of the row. |
source | FileSource | yes | Where 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)
| Name | Type | Required | Description |
|---|---|---|---|
paths | array of string | no, 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_types | array of string | no, default [] | Report types to skip, like OutdatedTable or InvalidReference. |
check_assembly_kit_only_references | boolean | no, default false | If references to tables only in the Assembly Kit are checked too. |
Result (DiagnosticsSummary)
| Name | Type | Required | Description |
|---|---|---|---|
total | integer | yes | Amount of results. |
by_level | map of integer | yes | Amount of results of each level. |
by_type | map of integer | yes | Amount of results of each report type. |
diagnostics.list
Returns results of the last check, optionally filtered.
Params (ListDiagnostics)
| Name | Type | Required | Description |
|---|---|---|---|
levels | array of DiagnosticLevel or null | no | If set, only results of these levels are returned. |
report_types | array of string or null | no | If set, only results of these report types are returned. |
pack | string or null | no | If set, only results of this pack are returned. |
path_prefix | string | no, default "" | Only results of files whose path starts with this are returned. |
offset | integer | no, default 0 | Amount of matching results to skip. |
limit | integer or null | no | Maximum amount of results to return. Defaults to 100. |
Result (DiagnosticList)
| Name | Type | Required | Description |
|---|---|---|---|
results | array of DiagnosticResult | yes | The results in the page. |
total | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Files whose path starts with this are affected. |
columns | array of string | no, default [] | If set, only these columns of the files are affected. |
report_types | array of string | no, 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.
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the problem. Empty for problems of the configuration. |
path | string | yes | Path of the file with the problem. Empty for problems of a whole pack or of the configuration. |
checked | string | yes | What was checked: db, loc, text, pack, config, dependency, portrait_settings, group_formations or anim_fragment_battle. |
report_type | string | yes | Type of the problem, like InvalidReference. Used to ignore it. |
level | DiagnosticLevel | yes | Severity of the problem. |
message | string | yes | Description of the problem. |
cells | array of array of integer | no | Table cells with the problem, as [row, column]. -1 means the whole row or column. |
columns | array of string | no | Names 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)
| Name | Type | Required | Description |
|---|---|---|---|
search | any | yes | The search, with its matches. |
first_ids | map of array of integer | yes | ID 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)
| Name | Type | Required | Description |
|---|---|---|---|
pattern | string | yes | Text to search, or a regular expression if use_regex is set. |
case_sensitive | boolean | no, default false | If the search is case sensitive. |
use_regex | boolean | no, default false | If the pattern is a regular expression. |
sources | array of FileSource | yes | Where to search: open packs, the game files, the parent packs, or the Assembly Kit tables. |
file_types | array of string or null | no | Types 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)
| Name | Type | Required | Description |
|---|---|---|---|
total | integer | yes | Amount of matches. |
files | integer | yes | Amount of files with matches. |
by_type | map of integer | yes | Amount of matches in each type of file. |
search.matches
Returns matches of the last search, optionally filtered.
Params (ListSearchMatches)
| Name | Type | Required | Description |
|---|---|---|---|
file_types | array of string or null | no | If set, only matches in these types of files are returned. |
path_prefix | string | no, default "" | Only matches in files whose path starts with this are returned. |
offset | integer | no, default 0 | Amount of matching matches to skip. |
limit | integer or null | no | Maximum amount of matches to return. Defaults to 100. |
Result (SearchMatchList)
| Name | Type | Required | Description |
|---|---|---|---|
matches | array of SearchMatch | yes | The matches in the page. |
total | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
replace_text | string | yes | Text to replace the matches with. |
matches | array of integer or null | no | IDs of the matches to replace. If not set, all the matches are replaced. |
Result (SearchReplaced)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths of the edited files. |
summary | SearchSummary | yes | Summary 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 aredb/<table_name>/ak_data.
Type SearchMatch
A match of a search.
| Name | Type | Required | Description |
|---|---|---|---|
id | integer | yes | ID of the match, used to replace it. IDs change after every search and replace. |
source | FileSource or null | no | Where the file with the match is. Not set for matches in the schema. |
path | string | yes | Path of the file with the match. Empty for matches in the schema. |
file_type | string | yes | Type of the file with the match, like DB or Text. |
details | any | yes | Where 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.
| Name | Type | Required | Description |
|---|---|---|---|
total | integer | yes | Amount of matches. |
files | integer | yes | Amount of files with matches. |
by_type | map of integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | no, 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)
| Name | Type | Required | Description |
|---|---|---|---|
notes | array of NoteEntry | yes | The notes. |
notes.add
Attaches a note to a file or folder of an open pack.
Params (AddNote)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | no, default "" | Path of the file or folder to attach the note to. Empty for the whole pack. |
message | string | yes | Text of the note. |
url | string or null | no | Link to attach to the note. |
id | integer or null | no | ID of a note attached to the same path, to replace it instead of adding a new one. |
Result (NoteEntry)
| Name | Type | Required | Description |
|---|---|---|---|
id | integer | yes | ID of the note, unique within its path. |
path | string | yes | Path of the file or folder the note is attached to. Empty for the whole pack. |
message | string | yes | Text of the note. |
url | string or null | no | Link attached to the note, if any. |
notes.delete
Deletes a note of an open pack.
Params (DeleteNote)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path the note is attached to. |
id | integer | yes | ID of the note. |
Result (Done)
Empty object: {}.
Types
Type NoteEntry
A note of a pack.
| Name | Type | Required | Description |
|---|---|---|---|
id | integer | yes | ID of the note, unique within its path. |
path | string | yes | Path of the file or folder the note is attached to. Empty for the whole pack. |
message | string | yes | Text of the note. |
url | string or null | no | Link 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)
| Name | Type | Required | Description |
|---|---|---|---|
options | map of boolean | yes | If 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
options | map of boolean | no, default {} | Optimizations to enable or disable, by name. The rest keep the server’s settings. See tools.optimizer_options for the names. |
Result (FilesChanged)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths of the added or edited files. |
deleted | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
Result (SiegeAiPatched)
| Name | Type | Required | Description |
|---|---|---|---|
message | string | yes | Description of what was patched. |
deleted | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
tile_maps | array of string | yes | Paths on disk of the tile maps. |
tiles | array of MapTile | no, default [] | Tiles to add. |
Result (FilesChanged)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths of the added or edited files. |
deleted | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
starting_id | integer | yes | Only ids from this one are changed. |
id_offset | integer | yes | Amount to add to each id. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
skeleton | string | yes | Name of the skeleton. |
Result (FilePaths)
| Name | Type | Required | Description |
|---|---|---|---|
paths | array of string | yes | The paths, sorted. |
tools.export_gltf
Exports a RigidModel to a glTF file, with its textures.
Params (ExportGltf)
| Name | Type | Required | Description |
|---|---|---|---|
file | FileRef | yes | The RigidModel. |
destination | string | yes | Path 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
path | string | yes | Path of the video. |
format | string | yes | New 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key 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)
| Name | Type | Required | Description |
|---|---|---|---|
game_folder | string | yes | Folder name of the game of the MyMod, like warhammer_3. |
name | string | yes | Name of the MyMod. |
sublime_support | boolean | no, default false | If a Sublime Text project is created. |
vscode_support | boolean | no, default false | If a VSCode config is created. |
gitignore | string or null | no | If set, a git repository is created with this .gitignore. |
Result (MyModCreated)
| Name | Type | Required | Description |
|---|---|---|---|
pack_path | string | yes | Path 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)
| Name | Type | Required | Description |
|---|---|---|---|
code | string | yes | Code of the Lua test file. |
campaign | string or null | no | Campaign whose vanilla scripts to load, like main_warhammer. If not set, only the script libraries and the mods are loaded. |
Result (LuaTestResults)
| Name | Type | Required | Description |
|---|---|---|---|
report | any | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the startpos tables. |
Result (StartposCampaigns)
| Name | Type | Required | Description |
|---|---|---|---|
campaigns | array of string | yes | Campaign keys, sorted. |
last_used | string or null | yes | Campaign 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the startpos tables. |
campaign | string | yes | Campaign to build the startpos for. |
process_hlp_spd_data | boolean | no, default false | If 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)
| Name | Type | Required | Description |
|---|---|---|---|
cancel | boolean | no, default false | If the build is cancelled instead of imported. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the startpos tables. |
campaign | string | yes | Campaign to check. |
Result (StartposValidation)
| Name | Type | Required | Description |
|---|---|---|---|
issues | array of StartposIssue | yes | The 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the startpos tables. |
campaign | string | yes | Campaign to generate the startpos for. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack to add the tables to. |
campaign | string | yes | Campaign whose startpos the tables are extracted from. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the startpos tables. |
campaign | string | yes | Campaign whose startpos is compared. |
Result (StartposComparison)
| Name | Type | Required | Description |
|---|---|---|---|
tables | array of StartposTableComparison | yes | One 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack with the startpos tables. |
campaign | string | yes | Campaign whose rows are kept. |
Result (FilesChanged)
| Name | Type | Required | Description |
|---|---|---|---|
added | array of string | yes | Paths of the added or edited files. |
deleted | array of string | yes | Paths of the deleted files. |
tools.plugin_scripts
Returns the plugin scripts in the scripts folder of the config.
Params (ListPluginScripts)
Empty object: {}.
Result (PluginScripts)
| Name | Type | Required | Description |
|---|---|---|---|
scripts | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
script | string | yes | Path of the script. |
paths | array of string | yes | Paths of the files and folders passed to the script. |
Result (PluginScriptRun)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths of the files read back into the pack. |
error | string or null | yes | Error 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)
| Name | Type | Required | Description |
|---|---|---|---|
source | string | yes | Source of the script. |
Result (LuaHovers)
| Name | Type | Required | Description |
|---|---|---|---|
hovers | array of LuaHover | yes | The hovers. |
ceo.traits
Returns the trait CEOs of the Assembly Kit tables.
Params (ListTraitCeos)
Empty object: {}.
Result (TraitCeos)
| Name | Type | Required | Description |
|---|---|---|---|
traits | array of [string, string] | yes | UUID and key of each trait CEO. |
ceo.add_entries
Adds CEO entries (armour, career, traits and their locs) to an open pack.
Params (AddCeoEntries)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
entries | array of CeoEntryData | yes | The entries. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
assembly_kit | string | yes | Root folder of the Assembly Kit. |
bob | string | yes | Path 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
assembly_kit | string | yes | Root folder of the Assembly Kit. |
Result (FilesEdited)
| Name | Type | Required | Description |
|---|---|---|---|
edited | array of string | yes | Paths of the edited files. |
Types
Type CeoEntryData
A CEO entry to add.
| Name | Type | Required | Description |
|---|---|---|---|
name | string | yes | Name of the entry, used to build the keys of its rows. |
option | string | yes | Kind of entry: unique for unique ones. |
element | string | yes | Element of the entry. |
gender | string | yes | Gender of the entry. |
traits | array of [string, string] | yes | UUID and key of each trait of the entry. |
expanded | boolean | yes | If the entry also starts with a sword and shield. |
Type FileRef
A file in one of the sources.
| Name | Type | Required | Description |
|---|---|---|---|
source | FileSource | yes | Where the file is. |
path | string | yes | Path 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 aredb/<table_name>/ak_data.
Type LuaHover
Docs of a symbol of a Lua script. Lines and columns are 0-based.
| Name | Type | Required | Description |
|---|---|---|---|
start_line | integer | yes | Line where the symbol starts. |
start_column | integer | yes | Column where the symbol starts. |
end_line | integer | yes | Line where the symbol ends. |
end_column | integer | yes | Column where the symbol ends. |
docs | string | yes | The docs, as rich text. |
Type MapTile
A tile of a map.
| Name | Type | Required | Description |
|---|---|---|---|
path | string | yes | Path on disk of the tile. |
folder | string | yes | Subfolder of the pack’s terrain tiles folder to add it to. |
Type StartposIssue
A problem in the tables a startpos is built from.
| Name | Type | Required | Description |
|---|---|---|---|
level | DiagnosticLevel | yes | error if the build fails or hangs, warning if it works but drops or ignores data. |
table | string | yes | Table, without the _tables suffix. |
row | string | yes | Key values of the row, joined by |, or its position (#n) if the table has no keys. |
column | string or null | yes | Column with the problem, if it’s about one. |
message | string | yes | What’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.
| Name | Type | Required | Description |
|---|---|---|---|
table | string | yes | Table, without the _tables suffix. |
match_ratio | number | yes | Share of the original values the startpos keeps, from 0 to 1. |
matched_rows | integer | yes | Rows found in both. |
missing_rows | array of string | yes | Keys of the original rows the startpos doesn’t keep. |
extra_rows | array of string | yes | Keys of the extracted rows the original tables lack. |
columns | array of StartposColumnComparison | yes | The compared columns with different values. |
Type StartposColumnComparison
How the values of one column of the matched rows compare.
| Name | Type | Required | Description |
|---|---|---|---|
column | string | yes | Column name. |
equal | integer | yes | Matched rows with the same value. |
different | integer | yes | Matched rows with a different value. |
examples | array of StartposValueDifference | yes | Some of the differences. |
Type StartposValueDifference
A value the startpos keeps differently from its table.
| Name | Type | Required | Description |
|---|---|---|---|
row | string | yes | Key of the row. |
original | string | yes | Value in the original table. |
extracted | string | yes | Value 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
source_language | string | yes | Language code of the source texts, like en. |
language | string | yes | Language 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack_name | string | yes | Name of the pack the translation is for. |
source_language | string | yes | Language code of the source texts, like en. |
language | string | yes | Language 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.falsemeans 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)
| Name | Type | Required | Description |
|---|---|---|---|
pack | string | yes | Key of the pack. |
source_language | string | no, default "EN" | Language the texts of the pack are written in, like EN. |
language | string | yes | Language to translate the texts to, like ES or DE. |
filter | TranslationFilter | no, default "all" | Which entries to return. |
offset | integer | no, default 0 | Amount of matching entries to skip. |
limit | integer or null | no | Maximum amount of entries to return. Defaults to 200. |
Result (Translations)
| Name | Type | Required | Description |
|---|---|---|---|
entries | array of TranslationEntry | yes | The entries in the page, sorted by key. |
total | integer | yes | Amount 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)
| Name | Type | Required | Description |
|---|---|---|---|
language | string | yes | Language of the texts to generate, like EN. |
Result (VanillaTextsAvailable)
| Name | Type | Required | Description |
|---|---|---|---|
available | boolean | yes | If they’re available, generated now or before. |
Types
Type TranslationEntry
The translation of a text.
| Name | Type | Required | Description |
|---|---|---|---|
key | string | yes | Loc key of the text. |
source | string | yes | Text in the source language. |
translation | string | yes | Text in the target language. Empty if it’s not translated yet. |
needs_retranslation | boolean | yes | If the source text changed since it was translated. |
automatic | boolean | yes | If the translation was made automatically, and still needs a review. |
removed | boolean | yes | If 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)
| Name | Type | Required | Description |
|---|---|---|---|
device_code | string | yes | Device 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)
| Name | Type | Required | Description |
|---|---|---|---|
login | string or null | yes | Login 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)
| Name | Type | Required | Description |
|---|---|---|---|
component | UpdateComponent | yes | What to check. |
Result (UpdateStatus)
| Name | Type | Required | Description |
|---|---|---|---|
available | boolean | yes | If there is an update. |
state | UpdateState | yes | Details of the state. |
version | string or null | no | Version 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)
| Name | Type | Required | Description |
|---|---|---|---|
component | UpdateComponent | yes | What 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)
| Name | Type | Required | Description |
|---|---|---|---|
job | integer | yes | ID of the job. |
Result (JobStatus)
| Name | Type | Required | Description |
|---|---|---|---|
job | integer | yes | ID of the job. |
method | string | yes | Method the job runs. |
Plus one of:
{ "state": "queued" }: Waiting for the requests before it to finish.{ "state": "running", "progress"?: …, "stage"?: … }: Running.progress(integerornull, no): How far along the job is, from 0 to 100, if it reports it.stage(stringornull, 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)
| Name | Type | Required | Description |
|---|---|---|---|
job | integer | yes | ID of the job. |
timeout_secs | integer or null | no | Maximum amount of seconds to wait. If the job hasn’t ended by then, its current state is returned. Defaults to 60. |
Result (JobStatus)
| Name | Type | Required | Description |
|---|---|---|---|
job | integer | yes | ID of the job. |
method | string | yes | Method the job runs. |
Plus one of:
{ "state": "queued" }: Waiting for the requests before it to finish.{ "state": "running", "progress"?: …, "stage"?: … }: Running.progress(integerornull, no): How far along the job is, from 0 to 100, if it reports it.stage(stringornull, 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)
| Name | Type | Required | Description |
|---|---|---|---|
job | integer | yes | ID of the job. |
Result (Done)
Empty object: {}.
Types
Type RpcError
A JSON-RPC error.
| Name | Type | Required | Description |
|---|---|---|---|
code | integer | yes | Numeric code of the error. See ApiError::code. |
message | string | yes | Human-readable description of the error. |
data | any | no | The 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
| Aspect | WebSocket (/ws) | MCP (/mcp) |
|---|---|---|
| Protocol | JSON-RPC 2.0 | MCP (JSON-RPC 2.0) |
| Transport | WebSocket | Streamable HTTP |
| Interaction model | Call methods | Call named tools |
| Slow operations | Return a job ID, followed by job.updated notifications | Wait up to 45 seconds for the job, then return its state |
| Session control | ?session_id= and session.disconnect | One shared session, removed after 5 minutes of inactivity if it has no open packs or unfinished jobs |
| Intended clients | Custom scripts, GUIs | AI 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
resultof the tool. - If it failed, the tool returns its error, like any other tool that fails.
- If it’s still running, call
wait_for_jobwith itsjobID, 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:
| URI | Content |
|---|---|
rpfm://games | Keys of the supported games. |
rpfm://enums/PFHFileType | Valid pack types. |
rpfm://enums/CompressionFormat | Valid compression formats. |
rpfm://enums/SupportedFormats | Valid video formats. |
rpfm://reference/initialization | How to set up a session. |
rpfm://reference/path_conventions | Common file paths inside packs. |
Typical Workflow
- Set the game:
set_gamewith the game key. This loads the schema and the dependencies. - Open a pack:
open_packwith the path of the pack, ornew_pack. Both return the pack’skey. - Browse files:
list_fileswith the pack as source. - Read data:
table_infoandtable_rowsfor DB and Loc tables,read_filefor anything else. - Modify data:
edit_tablefor tables,write_filefor anything else. - 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
| Tool | Method | Description |
|---|---|---|
session_status | session.status | Get 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_game | session.set_game | Select the game to work with, like warhammer_3, loading its schema and, by default, its dependencies (vanilla files, Assembly Kit tables, parent packs). |
job_status | job.status | Get the state of a job: queued, running (with its current step), finished (with its result), failed (with its error) or cancelled. |
wait_for_job | job.wait | Wait for a job to end, up to timeout_secs (60 by default), and return its state, or its error if it failed. |
cancel_job | job.cancel | Cancel a job that hasn’t started yet. |
Packs
| Tool | Method | Description |
|---|---|---|
pack_info | pack.info | Get the details of an open pack: type, format version, compression, encryption flags, the packs it depends on, and its MyMod mode. |
pack_settings | pack.settings | Get 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_settings | pack.update_settings | Change settings of an open pack. |
new_pack | pack.new | Create a new empty pack. |
open_pack | pack.open | Open one or more packs from disk, merged into a single one. |
open_vanilla_packs | pack.open_vanilla | Open all the vanilla packs of the selected game, merged into a single one, to browse them like any open pack. |
close_pack | pack.close | Close an open pack. |
close_all_packs | pack.close_all | Close all open packs. |
save_pack | pack.save | Save an open pack to disk, to its current path or to a new path. |
update_pack | pack.update | Change properties of an open pack: type, compression, encryption and timestamp flags, the packs it depends on, and its MyMod mode. |
Files
| Tool | Method | Description |
|---|---|---|
list_files | files.list | List the files of an open pack, the game files, the parent packs, or the Assembly Kit tables, sorted by path. |
read_file | file.read | Read 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_file | file.write | Replace 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_animpack | animpack.list | List the files inside an AnimPack of an open pack, the game files or the parent packs. |
add_to_animpack | animpack.add | Copy files of an open pack into an AnimPack of an open pack. |
extract_from_animpack | animpack.extract | Copy files of an AnimPack of any source into an open pack. |
delete_from_animpack | animpack.delete | Delete files from an AnimPack of an open pack. |
create_file | files.create | Create 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_disk | files.add_from_disk | Add files and folders from disk to an open pack, under a folder of the pack. |
copy_files | files.copy | Copy 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_files | files.delete | Delete files and folders from an open pack. |
rename_files | files.rename | Rename or move files and folders of an open pack. |
duplicate_files | files.duplicate | Copy files of an open pack in the same pack, adding a number to their names. |
extract_files | files.extract | Extract 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
| Tool | Method | Description |
|---|---|---|
table_info | table.info | Get 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_rows | table.rows | Read rows of a DB or Loc table, from any source, as plain values (booleans, numbers and strings). |
column_values | table.column_values | Get 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_numbers | table.unused_numbers | Get 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_tables | table.merge | Merge tables of the same type of an open pack into a new one. |
upgrade_table | table.upgrade | Update a table of an open pack to the version it has in the game files, after a game update. |
rename_key | table.rename_key | Change 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_deletes | table.add_key_deletes | Add 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_tsv | table.export_tsv | Write a table of an open pack, the game files or the parent packs to a TSV file, to edit it in a spreadsheet. |
import_tsv | table.import_tsv | Replace a table of an open pack with the contents of a TSV file, keeping its GUID. |
edit_table | table.edit | Edit 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
| Tool | Method | Description |
|---|---|---|
table_definition | schema.definition | Get the columns of a table as defined in the schema, without needing a file of it. |
schema_tables | schema.tables | List the tables of the selected game’s schema whose name starts with a prefix, with the versions it has definitions for, newest first. |
patch_column | schema.patch_column | Change 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_patches | schema.remove_patches | Remove the local schema patches of a table, or of one of its columns. |
update_schemas | schema.update | Download the latest schemas, reload the selected game’s one, and rebuild the dependencies. |
update_schema_from_assembly_kit | schema.update_from_assembly_kit | Update the selected game’s schema with the table definitions of its Assembly Kit, and save it. |
raw_definitions | schema.raw_definitions | Get the definitions of a table as the schema stores them, to edit them with set_definition. |
set_definition | schema.set_definition | Add a definition to the selected game’s schema, or replace the one with its version, then save and reload the schema. |
delete_definition | schema.delete_definition | Remove a definition from the selected game’s schema, then save and reload the schema. |
referencing_columns | schema.referencing_columns | Get the columns of other tables referencing each column of a table, according to the schema. |
table_patches | schema.patches | Get 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_definitions | schema.missing_definitions | List the tables of an open pack with rows the schema can’t decode, which need a new definition. |
import_patches | schema.import_patches | Add patches to the selected game’s schema itself, and save it. |
Dependencies
| Tool | Method | Description |
|---|---|---|
generate_dependencies_cache | dependencies.generate_cache | Generate the dependencies cache of the selected game from its files and Assembly Kit, and load it. |
rebuild_dependencies | dependencies.rebuild | Reload the dependencies of the selected game, like after changing the packs the open packs depend on. |
dependency_tables | dependencies.tables | List 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
| Tool | Method | Description |
|---|---|---|
find_definition | references.definition | Find the row where a value of a referenced table is defined, like the row of factions_tables with a faction key. |
find_usages | references.usages | Find the rows of other tables referencing a value of a table, like everything using a faction key. |
find_loc | references.loc | Find the row of a Loc file with a key. |
row_locs | references.row_locs | Get 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_source | references.loc_source | Get 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_values | references.values | Get 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. |
Diagnostics and search
| Tool | Method | Description |
|---|---|---|
run_diagnostics | diagnostics.run | Check the open packs for problems (invalid references, duplicated keys, outdated tables, script errors, etc.), and return a summary by level and type. |
list_diagnostics | diagnostics.list | List 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_diagnostics | diagnostics.ignore | Make the next diagnostics checks of a pack skip results of files under a path, optionally only for some columns and report types. |
run_search | search.run | Search 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_matches | search.matches | List 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_matches | search.replace | Replace matches of the last run_search by ID, or all of them, with a text. |
run_lua_tests | lua.run_tests | Run Lua tests against the scripts of all open packs, outside of the game. |
Notes
| Tool | Method | Description |
|---|---|---|
list_notes | notes.list | Get the notes (comments) attached to a file or folder of an open pack, or all of them. |
add_note | notes.add | Attach a note (comment, with an optional link) to a file or folder of an open pack, or replace one by passing its id. |
delete_note | notes.delete | Delete a note of an open pack. |
Translations
| Tool | Method | Description |
|---|---|---|
list_translations | translations.list | Get the translation of the texts of an open pack to a language, reusing vanilla and previous translations. |
generate_vanilla_texts | translations.generate_vanilla | Generate the vanilla texts of a language from the game’s locale packs, so translations to and from it can reuse them. |
Tools
| Tool | Method | Description |
|---|---|---|
optimizer_options | tools.optimizer_options | Get the optimizer options as the server’s settings have them, by name. |
patch_siege_ai | tools.patch_siege_ai | Patch the siege maps of an open pack so the AI can use them. |
pack_map | tools.pack_map | Add the tiles and tile maps of a map exported by Terry to an open pack. |
generate_missing_locs | tools.generate_missing_locs | Add empty loc entries for the localised columns of the tables of the open packs that don’t have them yet. |
update_anim_ids | tools.update_anim_ids | Offset the animation ids of an open pack from a starting id, like after a game update moves them. |
anims_by_skeleton | tools.anims_by_skeleton | Get the paths of the animations using a skeleton, in the open packs and the dependencies. |
export_gltf | tools.export_gltf | Export a RigidModel of any source to a glTF file, with its textures. |
set_video_format | tools.set_video_format | Change the format of a ca_vp8 video of an open pack: CaVp8 or Ivf. |
live_export | tools.live_export | Export the scripts and UI files of an open pack to the game’s data folder, to test them without saving the pack. |
init_mymod | tools.init_mymod | Create 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_campaigns | startpos.campaigns | Get the campaigns a startpos can be built for, and the one the pack’s last startpos was built for. |
start_startpos | startpos.start | Start building a startpos for a campaign with the tables of an open pack: prepares the Assembly Kit and launches the game. |
finish_startpos | startpos.finish | Finish building a startpos after the game was closed: imports it into the pack, or cancels the build with cancel: true. |
validate_startpos | startpos.validate | Check 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_startpos | startpos.generate | Generate 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_tables | startpos.extract_tables | Extract 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_campaign | startpos.keep_campaign | Remove 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_tables | startpos.compare_tables | Compare the tables extracted from a campaign’s startpos with the tables it’s built from, to find what the build loses or changes. |
optimize_pack | tools.optimize | Remove data of an open pack that’s identical to vanilla or unneeded (duplicated rows, unchanged rows and files, empty tables, etc.). |
Updates
| Tool | Method | Description |
|---|---|---|
check_update | updates.check | Check 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_update | updates.apply | Download 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:
- Connect to
ws://127.0.0.1:45127/wsusing any WebSocket library, and wait for thesession.connectednotification. - Send JSON-RPC requests:
{ "jsonrpc": "2.0", "id": <number>, "method": "<method>", "params": { ... } }. - Receive messages: the ones with an
idare responses, matched to their request by it; the ones with amethodare notifications. - Handle errors by their
data.kind, not their message. - Wait for jobs: methods running as jobs return
{ "job": <id> }. Calljob.waituntil the job’sstateisfinished,failedorcancelled, or follow itsjob.updatednotifications. - Call
session.disconnectbefore 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 —
StableorBeta. - 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
@Nsuffix 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.bakholds the last copy that loaded successfully. Ifsettings.jsonis 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) orCtrl+Shift+P(command palette) and type the name of what you want. If the action exists, you can launch it from there.
Pack Menu
| Action | Default |
|---|---|
| New Pack | Ctrl+N |
| Open Packs | Ctrl+O |
| Open & Merge Packs | Ctrl+Shift+O |
| Save All Packs | Ctrl+S |
| Save Current Pack | Ctrl+Shift+S |
| Install Pack | Ctrl+Shift+I |
| Uninstall Pack | Ctrl+Shift+U |
| Load All CA Packs | Ctrl+G |
| Settings | Ctrl+, |
| 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
| Action | Default |
|---|---|
| Close Tab | Ctrl+W |
| Previous Tab | Ctrl+Shift+Tab |
| Next Tab | Ctrl+Tab |
| Toggle Quick Notes | (unbound) |
| Import From Dependencies | (unbound) |
| Move to Other Pane | (unbound) |
| Merge Panes | (unbound) |
View menu
| Action | Default |
|---|---|
| Toggle Global Search | Ctrl+Shift+F |
| Toggle Pack Contents | (unbound) |
| Toggle Diagnostics | (unbound) |
| Toggle Dependencies | (unbound) |
| Toggle References | (unbound) |
Command palette
| Action | Default |
|---|---|
| Open file palette | Ctrl+P |
| Open command palette | Ctrl+Shift+P |
Pack tree (context-scoped)
| Action | Default |
|---|---|
| Add File | Ctrl+A |
| Add Folder | Ctrl+Shift+A |
| Add From Pack | Ctrl+Alt+A |
| New Folder | Ctrl+F |
| New DB | Ctrl+D |
| New Loc | Ctrl+L |
| New Text | Ctrl+T |
| New Quick File | Ctrl+Q |
| New AnimPack / Portrait Settings | (unbound) |
| Merge Files | Ctrl+M |
| Delete | Del |
| Extract | Ctrl+E |
| Rename / Move | Ctrl+R or F2 |
| Copy | Ctrl+Shift+C |
| Cut | Ctrl+X |
| Paste | Ctrl+V |
| Duplicate | Ctrl+Shift+D |
| Open with Decoder | Ctrl+J |
| Open with External Program | Ctrl+K |
| Open Pack Notes | Ctrl+Y |
| Expand All | Ctrl++ |
| Collapse All | Ctrl+- |
Table editor (DB / Loc)
| Action | Default |
|---|---|
| Add Row | Ctrl+Shift+A |
| Insert Row | Ctrl+I |
| Delete Row | Ctrl+Del |
| Delete Filtered-Out Rows | Ctrl+Shift+Del |
| Clone & Insert Row | Ctrl+D |
| Clone & Append Row | Ctrl+Shift+D |
| Copy | Ctrl+C |
| Copy as LUA Table | Ctrl+Shift+C |
| Paste | Ctrl+V |
| Paste as New Row | Ctrl+Shift+V |
| Rewrite Selection | Ctrl+Y |
| Invert Selection | Ctrl+- |
| Search | Ctrl+F |
| Undo / Redo | Ctrl+Z / Ctrl+Shift+Z |
| Smart Delete | Del |
| Find References / Go To Definition / Go To File / Patch Columns / Generate IDs / Import TSV / Export TSV / etc. | (unbound) |
Decoder
| Action | Default |
|---|---|
| Move Field Up | Ctrl+Up |
| Move Field Down | Ctrl+Down |
| Move Field Left | Ctrl+Left |
| Move Field Right | Ctrl+Right |
| Delete Field | Ctrl+Del |
| Delete Definition | Ctrl+Del |
| Load Definition | Ctrl+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.
| Game | Game key | Editing | Notes |
|---|---|---|---|
| Total War: Shogun 2 Complete Edition | shogun_2_complete_edition | Full | |
| Total War: Pharaoh – Dynasties | pharaoh_dynasties | Full | |
| Total War: Pharaoh | pharaoh | Full | |
| Total War: Warhammer 3 | warhammer_3 | Full | First game with twad_key_deletes (patch 6.3+). |
| Total War Saga: Troy | troy | Full | |
| Total War: Three Kingdoms | three_kingdoms | Full | |
| Total War: Warhammer 2 | warhammer_2 | Full | |
| Total War: Warhammer | warhammer | Full | |
| Total War Saga: Thrones of Britannia | thrones_of_britannia | Full | |
| Total War: Attila | attila | Full | |
| Total War: Rome 2 | rome_2 | Full | |
| Total War: Shogun 2 | shogun_2 | Full | |
| Total War: Napoleon | napoleon | Full | No Assembly Kit ever shipped — RPFM uses archived AK definitions. |
| Total War: Empire | empire | Full | No Assembly Kit ever shipped — RPFM uses archived AK definitions. |
| Total War: Arena | arena | Read-only | Discontinued; 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
Lzma1for non-table files. Warhammer 3 addsLz4andZstdon top ofLzma1. Shogun 2 Complete Edition supports onlyLz4Block, its own raw LZ4 variant for PFH3 Packs. Anything else older than Warhammer 1 supports no compression at all. twad_key_deletesis 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_*.csvshipped 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 formatched_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:
- Open one of the affected files in the DB Decoder.
- Adjust the column types until the first row decoded field shows sane values.
- Save the definition into the schema.
- Try to open the table again and see if it opens correctly.
- 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.FieldandFieldType— 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):
| Flag | Default | What it enables |
|---|---|---|
enable_tools | yes | Compiles the integrated Tools menu (Translator, Faction Painter, Unit Editor). |
support_model_renderer | no | 3D RigidModel preview. Requires the renderer library on the link path. |
support_uic | no | Propagates UIC parsing support through rpfm_lib. |
strict_subclasses_compilation | no | Forces a compilation failure if qt_rpfm_subclasses fails to build. |
only_for_the_brave | no | Exposes 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.