Asset API ​
This is the reference for asset ops: the operations an addon lists in operations.assets or returns from a build hook. Each op makes one change: to an asset group, an archive resource, a database record, a language string, a loose file or a track. The launcher's asset planner brings together every op in your profile, then delivers each kind by rewriting game files at build or through swofty_core while the game boots.
You'll find the types in the swofty-addon crate (packages/addon-api), module swofty_addon::asset. For a walkthrough of each kind, see Changes at runtime.
Op files ​
List your op files under operations.assets in swofty.json:
"operations": {
"assets": ["assets/livery.json"]
}Inside each file, the prefix on an op names its kind:
{
"schema_version": 1,
"ops": [
{ "op": "erp.put-resource", "erp": "2025_asset_groups/f1_2026_vehicle_package/teams/toro_rosso/wep/toro_rosso.erp",
"resource": "eaid://f1_2026_vehicle_package/teams/toro_rosso/wep/toro_rosso.wep", "source": "erp/car.erp" },
{ "op": "db.set", "select": "entrants.teams.team[_metadata_=toro_rosso_26]", "field": "display_name_code", "value": "AT" },
{ "op": "lang.set", "language": "*", "key": "lng_team_tor_hashtag", "value": "AlphaTauri" },
{ "op": "file.put", "source": "files/sponsor.dds", "target": "livery_package_shared/sponsor.dds" },
{ "op": "warn", "message": "Racing Bulls livery" }
]
}| Op | Fields | Change |
|---|---|---|
group. | group, erp or erps, optional deps, template | Add an asset group to the engine's asset manifest |
erp. | erp, resource, source, optional as | Copy a resource from an archive in the addon into a game archive, replacing or adding it |
erp., erp., erp. | As in ERP operations | Replace one fragment of a resource with a raw file, add a resource from raw fragment files, or remove a resource |
erp., erp. | As in ERP operations | Copy a resource, or edit a !pkg resource in place |
erp. | erps (target archives), source (a ready overlay archive) | Put every resource in a ready archive into each target, as a put-resource of each would. On the runtime route the archive loads as it is |
erp. | erp, resource, source (an archive in the addon) | Write the slices of a texture array that differ from the game's over the array in erp (Texture arrays) |
erp. | source, target | Copy a mip file, or a folder of them, into the game folder |
db., db. | As in database operations | Change a field, or copy a row |
db., db. | As in database operations | Built |
db. | As in database operations | Not built yet: a build with one fails |
db. | key (a path, or 0x and 16 hex digits), type (the tag), value (hex bytes) | Write one raw record |
db. | key, optional allow_stock_removal | Remove one record. A stock record stays unless allow_stock_removal is true (Removing stock records) |
lang. | language (a code, or * for every language), key, value | Set one string |
lang. | language, source (an . file) | Set every string in a table |
lang. | language, key, value | Set one string before every lang. and lang., which win over it (In-game text) |
file. | source, target (a path in the game folder) | Replace or add a loose game file |
track. | source (a folder holding an unpacked track package) | Activate a track before every database op (A track with its own name) |
item. | id, name, optional author, version, description | Show a read-only tile in the in-game ADDON SETTINGS screen, such as one per mod a build hook merges |
warn | message | Print a message at build |
- Files you list in
operations.databaseandoperations.erpload into the same model, withdb.anderp.added to each op. Eachoperations.filesentry becomes afile.put. - You can use
whenand{{id}}from your addon'sdbsettings in every op, just as you would in database operations. - Keep
patch-pkgedits toclone-entryandset-value. Other edits aren't built: if your addon uses one, the build skips all itserpops and prints a warning.
Sources ​
Use a path in your addon's folder for source, or an object to name another root:
"source": "erp/car.erp"
"source": { "root": "data", "path": "library/my-mod/files/car.erp" }root | Folder |
|---|---|
addon (a plain string) | $DATA/addons/<id>/ |
data | $DATA/addon-data/<id>/ |
cache | $DATA/cache/addons/<id>/ |
Keep each path inside its folder; a path that leaves it fails the build. The build hash covers every op, plus the size and modification time of each source it names. For a folder source, that includes every file in the folder.
Deliveries ​
The planner chooses one of three routes for each kind:
| Route | Meaning |
|---|---|
| Build | The build rewrites the game file and installs it through the journal |
| Runtime | The build writes swofty/assets., and swofty_core applies it in memory while the game boots. Game files stay stock. |
| Waiting | No delivery exists for the kind. The build lists the op and its origin |
To see which runtime features your installed loader offers, look in swofty/addons/core/features.json:
{ "schema": 1, "features": { "group": "stable", "erp": "stable", "db": "stable", "lang": "stable", "file": "experimental" } }| Kind | Build delivery | Runtime feature | Route |
|---|---|---|---|
group | none | stable | Runtime |
erp | the archive is rewritten, unchanged resources copied byte for byte | stable | Runtime |
db | gamedatabases.2025. is rewritten | stable | Runtime |
lang | none | stable | Runtime |
file | the file is copied into the game folder | experimental | Build, or Runtime when turned on |
- A
stablekind takes the runtime route. - To use the runtime route for an
experimentalkind, name it in"runtime_assets"in$DATA/config.json, for example"runtime_assets": ["file"]. The build then lists it infeaturesinswofty/assets.json, which turns it on in swofty_core. - To keep kinds with a build delivery (
erp,db,file) on the build route even when they arestable, name them in"build_assets"in$DATA/config.json, for example"build_assets": ["erp", "db"]. - Otherwise a kind with a build delivery is built, and a kind without one waits. The build prints
<origin>: <n> lang op(s) wait for lang support in swofty_core, or says the feature is experimental and not turned on. - A loader without
features.jsonhas no runtime features. If you runswofty buildwithout the loader, it buildserp,dbandfileand listsgroupandlangas waiting. - Every op of one kind takes the same route in a build, with one exception: an archive whose
erpchanges alter a stock resource's type, argument or fragment sizes, such as a resized scene, is rewritten into the game files while the rest stay on the runtime route. The build printserp: <archive> built into the game files; <n> resource(s) change their stock layout, .... - If your
erpbuild includes anerp.remove-resource, it goes into the game files: swofty_core cannot take a resource out of a stock archive. The build printserp: built into the game files; <n> resource removal(s) cannot apply at runtime. erp.add-mipmapsfiles go into the game folder through the journal on both routes, as a track's mip files do.- An
erp.overlayarchive is listed inresource_erpsas it is on the runtime route, without its resources being read or written again. If one of its resources would change a target's stock layout, or theerpbuild takes the build route, each resource in it goes in as anerp.put-resourcewould, and the build printserp: <origin>: its overlay archive goes in resource by resource; ...for the first case. An addon that writes the same overlay each time, such as one made when a mod is imported, keeps the build hash the same between launches. - If your
dbbuild removes records, writes a record of tag 10, 15 or 16, or writes one whose value is not text, it goes into the game file instead, with a warning.
Order and conflicts ​
Your profile's ops apply in this order:
- Tracks: every
track.addop, from the Team Simplified library or any addon. Their database rows and archives come first. - Addons in your profile's load order.
- Within an addon, its op files in
operationsorder (database, thenerp, thenassets), thenoperations.files, then the ops its build hook returns.
A later write wins, so load order matters when addons write to the same target. The planner records these targets:
| Target | Written by |
|---|---|
| An asset group | group. |
| A resource in an archive | erp., erp., erp., erp., erp., erp., erp. (each resource its archive holds, in each of its erps) |
| A database record | db., db., db. |
| A string in a language | lang. |
| A game path | file., erp. (its target) |
A file.put of a whole archive writes every resource that ERP ops write in that archive. Ops from the addon that puts it, or from addons after it in load order, apply to the archive it puts. A put after them in load order replaces their changes.
If one origin has already written to a target, replacing it counts as a conflict. That applies to erp.put-resource, erp.replace-resource, erp.remove-resource, erp.overlay, erp.add-mipmaps, db.set, db.put-record, db.remove-record, lang.set and file.put. By contrast, erp.add-resource, erp.copy-resource, erp.patch-pkg, group.register and added rows compose. After its stages, the build prints a line per addon with what it writes, then a warning per pair of addons in conflict, the winner first:
Scuderia AlphaTauri: 412 database values, 3 archive resources, 2 files, 5 text strings
Scuderia AlphaTauri overrides Example Livery on 12 database values and 1 fileA conflict between two items of one addon, from ops its build hook returns, prints the target and both origins, each an addon id with the item in brackets:
conflict: text lng_team_tor_hashtag (eng): example.pack (alphatauri) overrides example.pack (base)Each build also writes the pairs, with the overridden items, to $DATA/cache/overlay/<profile>/conflicts.json (swofty conflicts).
In the game ​
You'll find the build's swofty/assets.json and its files in the loader folder swofty/ inside the game folder. Paths in that JSON are relative to swofty/.
| Kind | swofty/assets. | Files |
|---|---|---|
group | asset_groups: group, erps, deps, template, origin | |
erp | resource_erps: one overlay archive per origin, as file, target erps and origin, listed from the last origin in load order to the first, since a group reads them in that order and the first that holds a resource wins | assets/resources/<n>., and assets/resources/p<n>. for erp. archives |
db | db_edits: each record that differs from the database after track activation, as hash, type and value, and "early_stage": true | |
lang | lang.<code>., and lang."*". for every language | assets/lang/<code>., assets/lang/all. |
file | "overlay": true, and "file" in features | overlay/<target> |
swofty_core applies each kind at a fixed point as the game boots:
| Kind | Applied | Core part |
|---|---|---|
group | when the engine parses its asset manifest. A group the stock manifest already has is skipped. | assets. |
erp | when the engine parses its asset manifest: every group listing a target archive reads the overlay archive first | resources. |
db | when the game database loads (Gdb_Load_GameDatabase) | gamedb. |
lang | when the game loads a language file (Loc_Load_LanguageFile), as a table added after the stock table | overlay. |
file | each time the game resolves a path under swofty/overlay/ | overlay. |
The early stage ​
The game builds its database before the window opens. To get swofty_core in before the database loads, "early_stage": true makes the loader load it inside the game's CreateMutexW("Codemasters F1 25") call. swofty_core still runs its window-stage handlers at the window trigger. The build sets early_stage whenever there are database edits; you can override it with SWOFTY_EARLY_STAGE=1 or 0.
- To load your plugin with swofty_core at the early stage, set
"early": trueon itsoperations.nativeentry. The build lists these plugins inearly_pluginsinswofty/settings.json; a nonempty list turns the early stage on. - Every other plugin loads at the window trigger, with a second mixin commit. A window-stage mixin that overlaps one installed at the early stage drops its addon.
Without the early stage, addons load after the database is built. At that point, only in-place int, float and bool overwrites of existing keys apply. New keys, strings and type changes are skipped and logged.
Experimental features ​
For an experimental feature to apply, features in swofty/assets.json must list it. Otherwise swofty_core skips it and logs:
[assets] overlay skipped: the runtime file feature is experimental and "features" in swofty/assets.json does not turn it onLogs ​
To see what happened to your runtime assets, read swofty/loader.log. swofty_core logs every step with the [assets] prefix: each group, each resource with its fragments and stock archives, each database edit (overwrote, created, retyped or skipped with the reason), each string table and each overlaid file. Without swofty/assets.json, it logs nothing and installs no hooks.
Native plugins ​
From a native plugin, you use the assets table added in SwoftyApi version 4, wrapped by the SDK's swofty/assets.hpp. Your calls feed the same lists in swofty_core as swofty/assets.json. Entries from assets.json apply first, followed by calls in plugin load order. See From a native plugin for an example.
namespace swofty::assets {
enum class Result { ok, late, unavailable, invalid };
struct Fragment {
std::string_view file;
std::string_view fourcc = {};
uint32_t flags = 0;
};
bool available(std::string_view feature);
Result register_group(std::string_view group, std::span<const std::string_view> erps,
std::span<const std::string_view> deps = {}, std::string_view templ = {});
Result put_resource(std::string_view uri, std::span<const Fragment> fragments,
std::span<const std::string_view> erps = {});
Result db_set(std::string_view key, std::string_view type, std::string_view value);
Result db_set(uint64_t hash, uint8_t tag, std::span<const std::byte> value);
Result lang_set(std::string_view language, std::string_view key, std::string_view text);
Result lang_table(std::string_view language, std::string_view file);
Result overlay_file(std::string_view game_path, std::string_view file);
}| Call | Kind | Same as | Accepted until |
|---|---|---|---|
register_group | group | group. | the engine parses its asset manifest |
put_resource | erp | a resources entry: the core builds the overlay archive | swofty_core writes the resource overlay, at the end of plugin init |
db_set | db | a db_edits entry | the game database loads. Only plugins with "early": true load before it |
lang_set, lang_table | lang | lang., lang. | the game loads its first language file |
overlay_file | file | file. | the game first reads that path |
| Result | Meaning |
|---|---|
ok | The entry joined the list |
late | The call came after its cutoff |
unavailable | swofty_core lacks the feature, or it is experimental and features in swofty/assets. does not turn it on, or the core offers an API version below 4 |
invalid | An argument is empty or malformed, or a file is missing |
- Plugin init includes every plugin's
SwoftyAddonInitandSWOFTY_ON_LOAD. The core waits until the last of them finishes before reading the lists. - After plugin init,
register_group,db_set,lang_set,lang_tableandoverlay_filejoin the core's lists directly, provided their cutoff hasn't passed and that kind already has something to apply. Otherwise they returnlate.put_resourcereturnslate. - At the window stage the game database is already built, so
db_setreturnslate. - To change strings after the language file loads, use string overrides instead of
lang_set. available(feature)takesgroup,erp,db,langorfile.db_settakes a key path, or0xand 16 hex digits.typeis adb_editstype name (int,float,string,enum,id,bool, ...) or a tag number. The raw form takes the hash and the tag, and its value bytes are the record's text.put_resourcefragments are files withfourccandflagsas in aresourcesentry; empty and 0 take the stock resource's values. Useerpsto name the target archives, or leave it out for the core to find the archives that hold the uri.- Give file paths relative to your addon's folder,
swofty/addons/<id>/. To have the build copy a file there, list it or its folder infileson your plugin'soperations.nativeentry. See Files in the game folder. A path that leaves the folder, or a missing file, returnsinvalid. - The core identifies your addon from the calling DLL through
dllsinswofty/settings.json. If no addon lists that DLL, every call that takes a file returnsinvalid. - You can trace every call in the log by its addon id and DLL:
[assets] api: example.livery (livery.dll) register_group asset_groups/example_package/showcar.assetgroup -> ok
[assets] api: example.livery (livery.dll) db_set entrants.teams.team.x = 1 -> late: the core loaded at the window stage, after the game database loadedA plugin that calls the API names itself in operations.native. Add "early": true to load it at the early stage:
"operations": {
"native": [{ "dll": "native/livery.dll", "early": true }]
}