Skip to content

Instantly share code, notes, and snippets.

@simi
Created July 22, 2026 22:07
Show Gist options
  • Select an option

  • Save simi/90e6ef974888aad28513225fadeec7f0 to your computer and use it in GitHub Desktop.

Select an option

Save simi/90e6ef974888aad28513225fadeec7f0 to your computer and use it in GitHub Desktop.
CWR Mod packing guide

Composing a mod for CWR

This guide is for mod authors: how to lay out a mod folder so CWR finds and loads each kind of content. It starts from a single addon and builds up to a pack that ships missions, templates, a campaign, an island with its own intro, and a replacement config.

For installing and enabling a finished mod, see the custom-mod guide. For where the game keeps missions, profiles, and saves, see the CWR folders guide.

Every layout below is exercised by a fixture in the test suite; the tests are named at the end of each section so a change to the engine that breaks the layout also breaks a test.

Contents

What makes a folder a mod

A directory is recognized as a mod when it directly contains at least one of these, matched without regard to case:

Root Holds
AddOns/ addon PBOs (models, configs, sounds)
Dta/ data overrides
Bin/ global config / resource / stringtable overrides
Campaigns/ campaign folders
Missions/ single-player and editor missions
MPMissions/ multiplayer missions
Templates/ multiplayer editor templates
SPTemplates/ single-player editor templates
anims/ intro/outro cutscene content
mod.json a metadata file (any of the above may be absent)

Only immediate children of the Mods root are scanned, so the marker directory must sit one level below the mod folder, not inside a wrapper. A pack that ships only player content, with no addon at all, is a valid mod: Missions/, MPMissions/, Templates/, SPTemplates/, or anims/ on their own are enough.

Every content root is additive. A mod's copy is listed next to the base game's, and when a mod and the base game provide the same name the mod's copy wins, matching the load side.

Tested by test_addon_content.cpp ("a content mod carrying only player content is recognized") and test_mod_collection.cpp.

Level 1: one addon

The classic mod is a single addon PBO under AddOns/.

@MyKit/
└── AddOns/
    └── mykit_core.pbo

The PBO keeps its config.cpp or config.bin at its own root. Its CfgPatches classes name the addons it provides; requiredAddons[] names dependencies and requiredVersion sets the minimum game version. CWR checks these after mounting every selected mod. An addon config is merged into the game's class tree; it does not replace anything (see Level 7 for the config that does).

Level 2: single-player missions

Single missions live under Missions/, in two forms that reach two different screens.

@MyKit/
└── Missions/
    ├── patrol.Eden/          # unpacked -> Custom Game (arcade) tree
    │   └── mission.sqm
    ├── ambush.Eden.pbo       # packed   -> single-mission browser
    └── CSLA/
        └── convoy.Eden.pbo   # a browsable "CSLA" subfolder
  • An unpacked Missions/<name>.<world>/ directory appears in the Custom Game tree under its world, next to the base game's editor missions.
  • A packed Missions/<name>.<world>.pbo appears in the single-mission browser. The browser is browse-into, so a Missions/<Category>/ subfolder shows up as a folder you can open.

<world> is the island class name (Eden, Abel, Noe, an addon island, ...).

Tested by test_addon_content.cpp ("arcade Custom Game tree ...", "SP mission browser scans a mod's Missions and its subfolders") and the Trident test tests/integration/ui/main_menu/sp_mod_missions.test.sqf.

Level 3: multiplayer missions

Multiplayer missions are packed PBOs under MPMissions/. They may sit at the root or one level down in a category folder; both are found by name.

@MyKit/
└── MPMissions/
    ├── coop10.Eden.pbo
    └── CSLA/
        └── tvt16.Eden.pbo

The list stays flat: a subfolder mission shows in the same list as a root one. The dedicated server resolves a mission by name across the root and each immediate subfolder, so a subfolder mission is hostable, and joining clients receive the mission PBO over the wire.

Tested by test_addon_content.cpp ("a content mod surfaces MP missions ...") and test_network_server.cpp ("GetMPMissionLookupDirectories includes a mod's MPMissions subfolders").

Level 4: editor templates

Editor templates are packed PBOs. Multiplayer templates go under Templates/, single-player templates under SPTemplates/.

@MyKit/
├── Templates/
│   └── mp_start.Eden.pbo
└── SPTemplates/
    └── sp_start.Eden.pbo

Both are listed additively in the mission wizard's template list for their world. As with missions, the load side prefers a mod's PBO, so a listed mod template also loads.

Tested by test_addon_content.cpp ("a content mod surfaces ... both template kinds") and test_mission_language_detector.cpp (mod-vs-base dedup).

Level 5: a campaign

A campaign is a folder under Campaigns/ holding its description.ext and its mission tree.

@MyKit/
└── Campaigns/
    └── ColdDawn/
        ├── description.ext
        └── Missions/
            └── m01.Eden/
                └── mission.sqm

Campaigns/ has always been a mod root and is unchanged by the content-root work; it is listed here for completeness.

Level 6: an island and its intro cutscene

An island is an addon like any other: the terrain .wrp, its textures, and a config.cpp declaring the world go inside a PBO under AddOns/. The world's config points at the objects it places (BIS core objects, or object addons you also ship).

The intro cutscene can travel inside the island's own PBO, with no loose folder on disk. Put the cutscene mission at <island>\intro.<world>\mission.sqm inside the island PBO and reference it from the world config:

# inside myisland.pbo (bank prefix "myisland")
myisland.wrp
config.cpp
intro.eden/
└── mission.sqm
class CfgWorlds
{
    class MyIsland : Intro
    {
        worldName  = "\myisland\myisland.wrp";
        cutscenes[] = {"..\addons\myisland\intro.eden"};
    };
};

The engine resolves the resulting anims/..\addons\myisland\intro.eden\mission.sqm lookup back into the island's mounted bank, so the packed folder loads directly. Older mods unpacked the intro to a loose addons/myisland/intro.eden/ on disk; that still works, but packing it into the island PBO is now enough.

Tested by test_qstream_banks.cpp ("Intro/anim cutscene path resolves from a packed addon bank").

Level 7: replacing the config (total conversion)

A normal addon merges into the game's config. A total conversion instead ships a complete master config in its top-level Bin/ that replaces the base outright, so vanilla units, vehicles, and groups the mod omits do not appear alongside the mod's set.

@MyKit/
└── Bin/
    ├── config.cpp          # replaces the base master config outright
    └── config-extra.cpp    # optional: adds classes on top after a replace

Because Bin/config.cpp replaces rather than merges, it must be a complete config: anything you still want (worlds, factions, the sides) has to be present. The remaster's own additions (for example CfgLanguages) live in a base config-extra.cpp that CWR re-applies on top after a replace, so those survive. Bin/ may also carry resource.cpp (UI) and stringtable.csv.

Do not confuse this top-level Bin/ with an addon's PBO-internal config: the addon config merges, the top-level Bin/config.cpp replaces.

Tested by test_config_replace.cpp.

mod.json

An optional top-level mod.json gives the mod a stable identity and clearer text in the MODS screen:

{
  "modId": "my-kit",
  "name": "My Kit",
  "version": "1.0"
}

modId is the identifier advertised in multiplayer and may be used in --mod in place of the folder name. mod.json alone is also enough to mark a folder as a mod, so a pure mission or template pack can carry one to be listed even without any content root.

The full layout

A pack can carry every part at once. This is the layout the test suite builds and checks end to end (test_addon_content.cpp, the @triContent fixture):

@MyKit/
├── mod.json
├── AddOns/
│   └── mykit_core.pbo
├── Bin/                     # only for a total conversion (Level 7)
│   ├── config.cpp
│   └── config-extra.cpp
├── Campaigns/
│   └── ColdDawn/
│       └── description.ext
├── Missions/
│   ├── patrol.Eden/         # unpacked -> arcade tree
│   │   └── mission.sqm
│   ├── ambush.Eden.pbo      # packed   -> browser
│   └── CSLA/
│       └── convoy.Eden.pbo
├── MPMissions/
│   ├── coop10.Eden.pbo
│   └── CSLA/
│       └── tvt16.Eden.pbo
├── Templates/
│   └── mp_start.Eden.pbo
├── SPTemplates/
│   └── sp_start.Eden.pbo
└── anims/                   # intro/outro content, or packed in the island PBO

Ship only the roots you use; each is optional and independent. Keep Bin/ out unless you really intend a total conversion, since it replaces the base config.

Testing your mod

Enable the mod and confirm each part surfaces where it should:

  • Custom Game tree: your unpacked Missions/<name>.<world>/ missions.
  • Single-mission browser: your packed Missions/*.pbo, browsing into subfolders.
  • New multiplayer host / server list: your MPMissions/*.pbo, including subfolders.
  • Mission wizard: your Templates/ and SPTemplates/ entries.
  • Campaigns list: your Campaigns/<Name>/.
  • The island's intro plays when its world loads.

For a non-interactive check that the mod mounts and the game initializes, run a launch with --check, which loads the selected mods and exits (see the CLI reference in the custom-mod guide).

Content-root reference

Root Content Form Reaches
AddOns/ addons *.pbo mounted as game content
Bin/ master config/resource/stringtable config.cpp, config-extra.cpp, resource.cpp, stringtable.csv replaces/extends global config
Campaigns/ campaigns <Name>/ folder Campaigns list
Missions/ single missions <name>.<world>/ (arcade) or <name>.<world>.pbo (browser), subfolders browsable Custom Game tree, single-mission browser
MPMissions/ MP missions <name>.<world>.pbo, one subfolder level MP mission list, server lookup
Templates/ MP templates <name>.<world>.pbo mission wizard
SPTemplates/ SP templates <name>.<world>.pbo mission wizard
anims/ or island PBO intro/outro <island>\intro.<world>\mission.sqm world intro cutscene
mod.json metadata modId, name, version MODS screen, MP identity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment