ERP operations
ERP operations describe changes to artwork and other resources in .erp archives. They are one kind of operation; for a walkthrough, see Textures and models. List these files in operations.erp in your manifest, or put the same ops in an operations.assets file with an erp. prefix (erp.put-resource).
swofty build applies every operation below. A patch-pkg edit other than clone-entry and set-value is not built, and an addon whose ERP files use one has its ERP files skipped with a warning.
Example
{
"schema_version": 1,
"ops": [
{
"op": "replace-resource",
"erp": "2025_asset_groups/ui_package/screens/fe_loading.erp",
"resource": "eaid://ui_package/art/textures/loading/background.tif",
"fragment": 0,
"source": "erp/background.tif"
}
]
}The resource name above is an example. List the real names with:
python3 tools/erp.py <archive> '*'erp paths are relative to the game folder. source paths are relative to your addon folder.
Operations
| Operation | Fields | Purpose |
|---|---|---|
replace-resource | erp, resource, fragment, source | Replace one fragment of a resource with a raw file; fragment defaults to 0 |
add-resource | erp, resource, type, sources | Add a resource, with one raw source file per fragment |
copy-resource | from_erp or source, erp, resource, optional as | Copy a resource between archives |
put-resource | erp, resource, source, optional as | Copy a resource from an archive in the addon, replacing a resource of the same name or adding it |
remove-resource | erp, resource | Remove a resource |
add-mipmaps | source, target | Copy a mip file, or a folder of them, to a game-relative path |
patch-pkg | erp, resource, fragment, edits | Run clone-entry and set-value edits on a !pkg resource |
overlay | erps, source | Put every resource in a ready archive into each of erps, replacing or adding it. On the runtime route the archive loads as it is (Asset API) |
merge-slices | erp, resource, source | Write the slices of a texture array that differ from the game's over the array already in erp (Texture arrays) |
warn | message | Print a message when the addon is built |
Raw resources
replace-resource and add-resource take raw fragment data: the bytes a fragment holds after decompression. The build compresses each fragment the way the archive stores it and updates its size and the resource hash.
replace-resourcereplaces fragmentfragmentofresourceinerpwith the filesourceand keeps the other fragments byte for byte. The source may be larger or smaller than the stock fragment. It fails when the archive has no such resource or the resource has no such fragment.add-resourceappendsresourcetoerp.typeis the resource type, such asGfxSurfaceRes, at most 15 characters.sourceslists one file per fragment, in order. The new resource takes its type argument, fragment names, flags and compression from the first resource inerpof the same type with the same number of fragments, and the operation fails whenerphas none. It also fails whenerpalready has a resource with that name;put-resourcereplaces or adds.
{
"op": "add-resource",
"erp": "2025_asset_groups/ui_package/screens/fe_loading.erp",
"resource": "eaid://ui_package/art/textures/loading/sponsor.tif.image",
"type": "GfxSurfaceRes",
"sources": ["erp/sponsor.head.bin", "erp/sponsor.pixels.bin"]
}Texture arrays
A texture array, such as overrides.team_logos.f1_2026.texarray.image, is a GfxSurfaceRes whose fragment 1 holds every slice, one after another, all the same size. Fragment 0 gives the slice count at offset 28. merge-slices reads resource from the archive source in your addon and writes only its changed slices:
{
"op": "merge-slices",
"erp": "2025_asset_groups/vfx_package/common.erp",
"resource": "eaid://ui_package/art/texture_arrays/overrides.uidb/overrides.team_logos.f1_2026.texarray.image",
"source": "logos/ui_textures.erp"
}- Each slice of
sourcethat differs from the game's stock slice replaces that slice of the array already inerp. That array is the stock one, or the one earlier addons in the load order wrote. - The game's stock array is the one in
erp. Whenerphas none, it is the one in the first archive, by name, directly in2025_asset_groups/<package>/, where<package>followseaid://inresource. The merged array is then added toerp. - An array with fewer slices than the game's, in the same format and slice size, merges the slices it has. Slices past the game's count are left out with a warning.
- An array of another format or slice size, a resource that is not a texture array, or a resource the game has no stock array for replaces the whole resource, with a warning.
- When the array in
erphas another layout from an earlier addon, the slices merge over the game's array instead, with a warning. - A
merge-slicesthat changes no slice leaveserpas it is.
The build prints one line per op:
erp: example.logos: 2 of 12 slice(s) of eaid://ui_package/art/texture_arrays/overrides.uidb/overrides.team_logos.f1_2026.texarray.image merged into 2025_asset_groups/vfx_package/common.erpConflicts count per slice. Two addons that change different slices of one array do not conflict. Two that change the same slice print <later addon> overrides <earlier addon> on 1 archive resource, and the later one in the load order wins. Each overridden slice counts as one archive resource, and swofty conflicts names it <erp>:<resource> slice <n>. A merge-slices that replaces the whole resource overrides every slice an earlier merge-slices wrote. A later put-resource, replace-resource, remove-resource or overlay of the resource overrides the addons that merged slices into it, and a later merge-slices into a resource an earlier addon wrote whole overrides that addon on each slice it changes.
On the runtime route the merged array goes into the overlay archive of the last addon that changed it. An overlay op whose archive holds a resource that a later merge-slices changes in one of its erps goes in resource by resource, and the build prints:
erp: example.pack: its overlay archive goes in resource by resource; a later erp.merge-slices merges into <resource> in <erp>A whole replacement that changes the layout of a stock entry in erp rewrites that archive into the game files and prints erp: <erp> built into the game files; ..., as for any other layout change.
The in-game loader patches only archives under 2025_asset_groups. Every ERP operation on any other archive, such as a shader package in shader_package_2025, merges into the game's copy of that archive in load order and writes it into the game files, and the build prints:
erp: shader_package_2025/object_flag.nefx2.sm51.erp built into the game files; the in-game loader patches only archives under 2025_asset_groupsRemoving resources
remove-resource takes resource out of erp. Removing a resource erp does not have is a no-op, logged. The in-game loader cannot take a resource out of a stock archive, so a build with any remove-resource delivers every ERP operation by rewriting the archives, and prints:
erp: built into the game files; 1 resource removal(s) cannot apply at runtimeScenes
A UI scene, the .idf layout a screen attaches to, is the UILayout resource named exactly <path>.idf, with no ?context=. Its single fragment is a !pkg scene. To ship an edited scene, replace that fragment with the edited file:
{
"op": "replace-resource",
"erp": "2025_asset_groups/ui_package/flow_career_hub.erp",
"resource": "eaid://ui_package/art/screens/career/rnd_overview.idf",
"source": "ui/rnd_overview.idf"
}When the resource is a UILayout, the build checks that source is a scene saved from that same .idf, and fails otherwise.
Several archives often carry the same scene. List one replace-resource per archive that holds it:
python3 tools/erp.py <archive> 'rnd_overview.idf' | grep UILayoutSwofty UI exports an edited scene with these operations for every archive that holds it. See Editing menus with an addon.
The game reads a scene from whichever archive loads it first. When the build resizes a resource that another stock archive in the same package folder also carries, it warns:
warning: erp: eaid://ui_package/art/screens/career/rnd_overview.idf is resized, but 2025_asset_groups/ui_package/flow_team_hq.erp also carries it and keeps the stock copy; replace it there too, or the game may show either oneMip files
A texture (GfxSurfaceRes) with three fragments keeps the rest of its mip chain in a loose file. Fragment 2 starts with a length byte and that file's path, relative to the game folder, such as f1_safetycar_package/safety_car_2/textures/safety_car_2_decal_da.tif.mipmaps.
add-mipmaps copies source to target in the game folder. A folder copies every file under it, keeping their paths below target. The files replace or add game files through the journal, on both routes. Point a texture at a new mip file by replacing fragment 2 with replace-resource, or by adding the texture with add-resource.
{ "op": "add-mipmaps", "source": "mipmaps/", "target": "f1_safetycar_package/safety_car_2/textures/" }Copying resources
copy-resource appends a resource to the archive erp, copying its parts byte for byte. It reads the resource from the game archive from_erp, or from source, an archive in your addon folder. as gives the copy a new name. The copy fails when erp already has a resource with that name.
{
"op": "copy-resource",
"source": "erp/car.erp",
"erp": "2025_asset_groups/f1_2026_vehicle_package/teams/toro_rosso/wep/toro_rosso_barcelona_26.erp",
"resource": "eaid://f1_2026_vehicle_package/teams/toro_rosso/wep/toro_rosso.wep",
"as": "eaid://f1_2026_vehicle_package/teams/toro_rosso/wep/toro_rosso_barcelona_26.wep"
}Run patch-pkg after the copy to change the copy's contents, such as the layer names a renamed world lists.
Editing !pkg resources
patch-pkg changes entries inside a !pkg resource, such as a track layer, and keeps the rest of the archive unchanged. Its edits work as in UI patches:
clone-entrycopies the entrysrcin the container atatand names the copyas. In an array,srcis a selector such as[name=vehicles_current_myteam$PitBox12], andasreplaces the selected field.setandset-valuepaths use dots and[field=value]selectors. A value is written in the type of the value it replaces: numbers for integers and floats, 16 numbers for amat4, a hex string for awoid.fromonclone-entrytakes the entry from another resource, given aserp,resourceandfragment, as shipped with the game.
{
"op": "patch-pkg",
"when": { "cars": 26 },
"erp": "2025_asset_groups/environment_package/tracks/bahrain/wep/bahrain_f1_26_myteam.erp",
"resource": "eaid://environment_package/tracks/bahrain/wep/bahrain-vehicles_current_myteam.layer",
"edits": [
{
"op": "clone-entry", "at": "instances",
"src": "[name=vehicles_current_myteam$PitBox12]", "as": "vehicles_current_myteam$PitBox13",
"set": {
"uniqueid": "8631efce0fbce521eb5b71c1d07dd616",
"overrides.VehPlacement.Id": 13,
"overrides.WorldObject.transform": [-0.9988, 0.0, -0.048, 0.0, 0.0, 1.0, 0.0, 0.0, 0.048, 0.0, -0.9988, 0.0, -350.3214, -15.4045, -190.421, 1.0]
}
}
]
}ERP files take when and settings like database operations. Only the archives a build changes are written to the overlay.
With the in-game loader, changed and added resources reach the game as overlay archives instead of rewritten archives (deliveries). Both routes produce the same resource bytes. The overlay can only carry a replaced resource whose type, argument and fragment layout, fragment sizes included, match the stock entry. An archive with any other replacement, such as an edited scene that grew, is rewritten into the game files instead, and the build prints:
erp: 2025_asset_groups/ui_package/flow_career_hub.erp built into the game files; 1 resource(s) change their stock layout, which the in-game loader cannot load, such as eaid://ui_package/art/screens/career/rnd_overview.idf: fragment 0 is 30684 bytes, the stock one 29596; stock archives that bind to the replacement read it with the stock layoutThe rest of the build's archives stay on the overlay. swofty restore puts a rewritten archive back like any other replaced game file. A resource copied from an archive with a different argument size is still left out, and the build warns.
For menu changes, see UI patches. For the archive format, see ERP archives.